<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://www.3dbrew.org/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Kynex7510</id>
	<title>3dbrew - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://www.3dbrew.org/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Kynex7510"/>
	<link rel="alternate" type="text/html" href="https://www.3dbrew.org/wiki/Special:Contributions/Kynex7510"/>
	<updated>2026-05-13T00:26:52Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.1</generator>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=CRO0&amp;diff=23758</id>
		<title>CRO0</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=CRO0&amp;diff=23758"/>
		<updated>2026-02-14T08:22:43Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Segment type&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:File formats]]&lt;br /&gt;
&lt;br /&gt;
CRO with extension .cro is used for &amp;quot;DLLs&amp;quot;. CRS with extension .crs is in the same format of CRO but storing the symbol information of the static module (the main application). The end of the file is aligned to a 0x1000-byte boundary with 0xCC bytes.&lt;br /&gt;
&lt;br /&gt;
The CRO hashes before the magic are hashed themselves and verified against the [[CRR0|CRR]] hash table.&lt;br /&gt;
&lt;br /&gt;
When the RO module loads the entire CRO into process memory (mapped in the 0x00100000-0x04000000 region), it relocates the mapped CRO data (eg. such that offsets can be dereferenced). The magic field is also changed to &amp;quot;FIXD&amp;quot; if fix level is not 0.&lt;br /&gt;
&lt;br /&gt;
Upon loading, the RO module will look for export symbol &amp;quot;nnroAeabiAtexit_&amp;quot; to patch it to its import symbol &amp;quot;__aeabi_atexit&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
For dumping symbols and loading a CRO into IDA, see [https://github.com/plutooo/ctr/] and [https://github.com/wwylele/IDA_plugin_CRO].&lt;br /&gt;
&lt;br /&gt;
= Structure =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| 0x20&lt;br /&gt;
| SHA-256 hash over the raw data from offset 0x80 to *(code offset), exclusive&lt;br /&gt;
|-&lt;br /&gt;
| 0x20&lt;br /&gt;
| 0x20&lt;br /&gt;
| SHA-256 hash over the raw data from *(code offset) to *(module name offset), exclusive&lt;br /&gt;
|-&lt;br /&gt;
| 0x40&lt;br /&gt;
| 0x20&lt;br /&gt;
| SHA-256 hash over the raw data from *(module name offset) to *(data offset), exclusive&lt;br /&gt;
|-&lt;br /&gt;
| 0x60&lt;br /&gt;
| 0x20&lt;br /&gt;
| SHA-256 hash over the raw data from *(data offset) to *(data offset + data size), exclusive (RO does NOT check this hash)&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| 0x04&lt;br /&gt;
| Magic &amp;quot;CRO0&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| 0x04&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x88&lt;br /&gt;
| 0x04&lt;br /&gt;
| Next loaded CRO pointer, set by RO during loading (Usually zero when the CRO is being loaded)&lt;br /&gt;
|-&lt;br /&gt;
| 0x8C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Previous loaded CRO pointer, set by RO during loading&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| 0x04&lt;br /&gt;
| File size&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| 0x04&lt;br /&gt;
| .bss size&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| 0x04&lt;br /&gt;
| Fixed size, set by RO after fixing, used to keep track of the new size&lt;br /&gt;
|-&lt;br /&gt;
| 0x9C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 0xA0&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset|Segment offset]] that is always the same as export symbol &amp;quot;nnroControlObject_&amp;quot;. 0xFFFFFFFF in CRS&lt;br /&gt;
|-&lt;br /&gt;
| 0xA4&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset|Segment offset]] for &amp;quot;OnLoad&amp;quot; function, which will be called when the module is initialized. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xA8&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset|Segment offset]] for &amp;quot;OnExit&amp;quot; function, which will be called when the module is finalized. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xAC&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset|Segment offset]] for &amp;quot;OnUnresolved&amp;quot; function, which will be called when an unresolved function is called. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xB0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Code offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xB4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Code size&lt;br /&gt;
|-&lt;br /&gt;
| 0xB8&lt;br /&gt;
| 0x04&lt;br /&gt;
| .data offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xBC&lt;br /&gt;
| 0x04&lt;br /&gt;
| .data size&lt;br /&gt;
|-&lt;br /&gt;
| 0xC0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Module Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xC4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Module Name size&lt;br /&gt;
|-&lt;br /&gt;
| 0xC8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Segment Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xCC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Segment Table num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0xD0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Export Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xD4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Export Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0xD8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Export Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xDC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Export Table num (size = num * 4)&lt;br /&gt;
|-&lt;br /&gt;
| 0xE0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Strings offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xE4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Strings size&lt;br /&gt;
|-&lt;br /&gt;
| 0xE8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Trie offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xEC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Trie num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0xF0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Module Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xF4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Module Table num (size = num * 20)&lt;br /&gt;
|-&lt;br /&gt;
| 0xF8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Relocations offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xFC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Relocations num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0x100&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x104&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x108&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x10C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x110&lt;br /&gt;
| 0x04&lt;br /&gt;
| Anonymous Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x114&lt;br /&gt;
| 0x04&lt;br /&gt;
| Anonymous Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x118&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Strings offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x11C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Strings size&lt;br /&gt;
|-&lt;br /&gt;
| 0x120&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Fields 0x120, 0x130|Unknown Relocations]] base parameter offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x124&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Fields 0x120, 0x130|Unknown Relocations]] base parameter num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x128&lt;br /&gt;
| 0x04&lt;br /&gt;
| Internal Relocations offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x12C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Internal Relocations num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0x130&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Fields 0x120, 0x130|Unknown Relocations]] offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x134&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Fields 0x120, 0x130|Unknown Relocations]] num (size = num * 12)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Segment offset ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bits&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| Segment index for table&lt;br /&gt;
|-&lt;br /&gt;
| 4-31&lt;br /&gt;
| Offset into segment&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Segment Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment offset &lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment size&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment type: 0 = executable (.text), 1 = read only (.rodata), 2 = read/write (.data), 3 = zero-initialized (.bss)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
RO expects only one of executable, read/write and zero-initialized segments, but there&#039;s no limit on read only segments.&lt;br /&gt;
&lt;br /&gt;
== Named Export Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for export&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Indexed Export Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for export&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Export Trie entry ==&lt;br /&gt;
&lt;br /&gt;
Each entry represents a node in the trie:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x2&lt;br /&gt;
| Flags (bits 0-2: bit index, bits 3-15: char index)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| 0x2&lt;br /&gt;
| Left node info (bit 0-14: node index, bit 15: is leaf)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x2&lt;br /&gt;
| Right node info (same as left node)&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x2&lt;br /&gt;
| Index for the named exports table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The root node is a dummy node, with the right child being a leaf and pointing to the empty string. Hence the search must be performed starting from the left child.&lt;br /&gt;
&lt;br /&gt;
Search is performed by extracting the bit of the input using informations encoded in the flags. If the bit is 0 or the offsets are out of bounds the search continues on the left, else on the right.&lt;br /&gt;
The search always yields a result, which may not match the input string, so a final strcmp call is required.&lt;br /&gt;
&lt;br /&gt;
== Named Import Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Indexed Import Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| index of the export symbol&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Anonymous Import Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] of the export symbol&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Import Module Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Module name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a sub list in Indexed Import Table&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| Indexed import num&lt;br /&gt;
|-&lt;br /&gt;
| 0xC&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a sub list in Anonymous Import Table&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| 0x4&lt;br /&gt;
| Anonymous import num&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Relocation entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for output.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x1&lt;br /&gt;
| Relocation type (R_ARM_NONE = 0, R_ARM_ABS32 = 2, R_ARM_REL32 = 3, R_ARM_THM_PC22 = 10, R_ARM_CALL = 28, R_ARM_JUMP24 = 29, R_ARM_TARGET1 = 38, R_ARM_PREL31 = 42)&lt;br /&gt;
|-&lt;br /&gt;
| 0x5&lt;br /&gt;
| 0x1&lt;br /&gt;
| For import relocations, non-zero if last entry; for internal relocations, this is the referred segment index&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x1&lt;br /&gt;
| For import relocations, 1 is written to first entry if all symbols loaded successfully; unknown (padding?) for internal relocations&lt;br /&gt;
|-&lt;br /&gt;
| 0x7&lt;br /&gt;
| 0x1&lt;br /&gt;
| Unknown (padding?)&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| addend&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Relocation code from RO:&lt;br /&gt;
&lt;br /&gt;
 static Result writeRelocation(u32* out, u32 relocType, u32 addend, u32 base, u32 inputPtr) {&lt;br /&gt;
     const s32 branchOffset = inputPtr - base;&lt;br /&gt;
     u32 offset = base + addend - inputPtr;&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_NONE)&lt;br /&gt;
         return 0;&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_ABS32 || relocType == R_ARM_TARGET1) {&lt;br /&gt;
         *out = base + addend;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_REL32) {&lt;br /&gt;
         *out = offset;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_THM_PC22) {&lt;br /&gt;
         // +-4MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x400000 || branchOffset &amp;lt;= -0x400000)&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         if (base &amp;amp; 1) {&lt;br /&gt;
             *out = (((offset &amp;gt;&amp;gt; 12) | 0xF000) &amp;lt;&amp;lt; 16) | ((offset &amp;lt;&amp;lt; 4) &amp;gt;&amp;gt; 5) | 0xF800;&lt;br /&gt;
         } else {&lt;br /&gt;
             if (offset &amp;amp; 2)&lt;br /&gt;
                 offset += 2;&lt;br /&gt;
 &lt;br /&gt;
             *out = ((offset &amp;gt;&amp;gt; 12) | 0xF000) &amp;lt;&amp;lt; 16 | ((offset &amp;lt;&amp;lt; 4) &amp;gt;&amp;gt; 5) | 0xE800;&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_CALL) {&lt;br /&gt;
         // +-32MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x2000000 || branchOffset &amp;lt;= -0x2000000)&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         if (base &amp;amp; 1) {&lt;br /&gt;
             *out = ((offset &amp;lt;&amp;lt; 23) &amp;amp; 0x1000000) | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8) | 0xFA000000;&lt;br /&gt;
         } else {&lt;br /&gt;
             *out = 0xEB000000 | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8);&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_JUMP24) {&lt;br /&gt;
         // +-32MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x2000000 || branchOffset &amp;lt;= -0x2000000 || (base &amp;amp; 1))&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         *out = (*out &amp;amp; 0xFF000000) | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8);&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_PREL31) {&lt;br /&gt;
         *out = addend + ((base &amp;lt;&amp;lt; 1) &amp;gt;&amp;gt; 1) - inputPtr;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     return 0xD9012C22;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
== Fields 0x120, 0x130 ==&lt;br /&gt;
&lt;br /&gt;
Field 0x130 is a list of relocations which serve an unknown purpose. Field 0x120 is a list of entries related to the previous list:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset to a relocation entry in field 0x130&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment offset used for calculating the &amp;quot;base&amp;quot; parameter&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= nnroControlObject =&lt;br /&gt;
&lt;br /&gt;
Signature:&lt;br /&gt;
&lt;br /&gt;
 Result nnroControlObject(void* arg, u32 type);&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Type&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Write address of &amp;quot;nnroEitNode_&amp;quot; to memory pointed by arg (*(u32*)arg = &amp;amp;nnroEitNode_)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Set global in CRO to arg, where arg is the CRO end (g_CRO_end = (u32)arg)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Write CRO end to memory pointed by arg (*(u32*)arg = g_CRO_end)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Write begin, end of function list (C initializers?) to memory pointed by arg (*(u32*)arg = (u32)listBegin, ((u32*)arg)[1] = (u32)listEnd)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Write begin, end of function list (C++ initializers?) to memory pointed by arg (*(u32*)arg = (u32)listBegin, ((u32*)arg)[1] = (u32)listEnd)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;quot;EitNode&amp;quot; are nodes of a linked list containing informations about the main executable + loaded modules:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Type&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| EitNode*&lt;br /&gt;
| Pointer to the previous element of the list&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| EitNode*&lt;br /&gt;
| Pointer to the next element of the list&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| u32&lt;br /&gt;
| Module code start, for the main executable this is .text base, for CROs this is the value at CRO+0xB0&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| u32&lt;br /&gt;
| Module code end (start + size), for the main executable the size is the size of all mapped sections, for CROs it&#039;s the value at CRO+0xB4&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| u32*&lt;br /&gt;
| Unknown, seems to be an array of u32, probably holds debugging informations&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| u32*&lt;br /&gt;
| This points at the end of the previous array&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ElfNode* (?)&lt;br /&gt;
| Unknown, this is only set for the main executable node, and it points to itself&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=CRR0&amp;diff=23716</id>
		<title>CRR0</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=CRR0&amp;diff=23716"/>
		<updated>2025-12-29T22:48:20Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Debug info&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:File formats]]&lt;br /&gt;
&lt;br /&gt;
CRR files are used for attestation of [[CRO0|relocatable modules]]. Each hash stored in the hash table is over the corresponding module&#039;s hash table.&lt;br /&gt;
&lt;br /&gt;
CRR files must be stored under &amp;quot;romfs:/.crr/&amp;quot;. The end of the file is aligned to a 0x1000-byte boundary with 0xCC bytes.&lt;br /&gt;
&lt;br /&gt;
= Structure =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| 0x04&lt;br /&gt;
| Magic &amp;quot;CRR0&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| 0x04&lt;br /&gt;
| Reserved&lt;br /&gt;
|-&lt;br /&gt;
| 0x08&lt;br /&gt;
| 0x04&lt;br /&gt;
| Next loaded CRR (must be zero in file, set by RO during loading)&lt;br /&gt;
|-&lt;br /&gt;
| 0x0C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Previous loaded CRR (must be zero in file, set by RO during loading)&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| 0x04&lt;br /&gt;
| Debug Info Offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x14&lt;br /&gt;
| 0x04&lt;br /&gt;
| Debug Info Size&lt;br /&gt;
|-&lt;br /&gt;
| 0x18&lt;br /&gt;
| 0x08&lt;br /&gt;
| Reserved&lt;br /&gt;
|-&lt;br /&gt;
| 0x20&lt;br /&gt;
| 0x04&lt;br /&gt;
| UniqueID Mask&lt;br /&gt;
|-&lt;br /&gt;
| 0x24&lt;br /&gt;
| 0x04&lt;br /&gt;
| UniqueID Pattern&lt;br /&gt;
|-&lt;br /&gt;
| 0x28&lt;br /&gt;
| 0x18&lt;br /&gt;
| Reserved&lt;br /&gt;
|-&lt;br /&gt;
| 0x40&lt;br /&gt;
| 0x100&lt;br /&gt;
| RSA-2048 modulo&lt;br /&gt;
|-&lt;br /&gt;
| 0x140&lt;br /&gt;
| 0x100&lt;br /&gt;
| RSA-2048 signature signed by Nintendo, starting at offset 0x20 with size 0x120&lt;br /&gt;
|-&lt;br /&gt;
| 0x240&lt;br /&gt;
| 0x100&lt;br /&gt;
| RSA-2048 signature verified with the above modulo, over the data starting at 0x340 until hash table end&lt;br /&gt;
|-&lt;br /&gt;
| 0x340&lt;br /&gt;
| 0x04&lt;br /&gt;
| Process UniqueID&amp;lt;&amp;lt;8, from the ProgramID&lt;br /&gt;
|-&lt;br /&gt;
| 0x344&lt;br /&gt;
| 0x04&lt;br /&gt;
| Size of the whole CRR file&lt;br /&gt;
|-&lt;br /&gt;
| 0x348&lt;br /&gt;
| 0x08&lt;br /&gt;
| Reserved&lt;br /&gt;
|-&lt;br /&gt;
| 0x350&lt;br /&gt;
| 0x04&lt;br /&gt;
| Hash table Offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x354&lt;br /&gt;
| 0x04&lt;br /&gt;
| Total hashes in the hash table&lt;br /&gt;
|-&lt;br /&gt;
| 0x358&lt;br /&gt;
| 0x04&lt;br /&gt;
| Offset to the plain region containing text tags, from the process [[NCCH#CXI|NCCH]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x35C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Size of plain region&lt;br /&gt;
|-&lt;br /&gt;
| 0x360&lt;br /&gt;
| &lt;br /&gt;
| SHA-256 hash table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Debug Info ==&lt;br /&gt;
&lt;br /&gt;
The debug info region starts with an header:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| 0x04&lt;br /&gt;
| Offset to a list of entries, one for each CRO module&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| 0x04&lt;br /&gt;
| Total number of entries&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The entries are ordered following the hash table, and are structured as such:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| 0x04&lt;br /&gt;
| Offset to a structure encoding the filename/path for a CRO&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| 0x04&lt;br /&gt;
| Unknown&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A CRO filename/path is structured as such:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| 0x04&lt;br /&gt;
| Offset to the filename/path data&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| 0x04&lt;br /&gt;
| Size (excluding null terminator, if any)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These informations are then used for setting up a CRO debug info structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| 0x04&lt;br /&gt;
| Virtual address of the filename/path data&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| 0x04&lt;br /&gt;
| Size of filename/path + 1&lt;br /&gt;
|-&lt;br /&gt;
| 0x08&lt;br /&gt;
| 0x04&lt;br /&gt;
| Mapping address of CRO&lt;br /&gt;
|-&lt;br /&gt;
| 0x0C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Mapping address for the .data segment, if any&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The info structure is passed to [[SVC|svcBreak]] with reason = LOAD_CRO or UNLOAD_CRO.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=CRO0&amp;diff=23712</id>
		<title>CRO0</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=CRO0&amp;diff=23712"/>
		<updated>2025-12-26T13:19:31Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: About hashes&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:File formats]]&lt;br /&gt;
&lt;br /&gt;
CRO with extension .cro is used for &amp;quot;DLLs&amp;quot;. CRS with extension .crs is in the same format of CRO but storing the symbol information of the static module (the main application). The end of the file is aligned to a 0x1000-byte boundary with 0xCC bytes.&lt;br /&gt;
&lt;br /&gt;
The CRO hashes before the magic are hashed themselves and verified against the [[CRR0|CRR]] hash table.&lt;br /&gt;
&lt;br /&gt;
When the RO module loads the entire CRO into process memory (mapped in the 0x00100000-0x04000000 region), it relocates the mapped CRO data (eg. such that offsets can be dereferenced). The magic field is also changed to &amp;quot;FIXD&amp;quot; if fix level is not 0.&lt;br /&gt;
&lt;br /&gt;
Upon loading, the RO module will look for export symbol &amp;quot;nnroAeabiAtexit_&amp;quot; to patch it to its import symbol &amp;quot;__aeabi_atexit&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
For dumping symbols and loading a CRO into IDA, see [https://github.com/plutooo/ctr/] and [https://github.com/wwylele/IDA_plugin_CRO].&lt;br /&gt;
&lt;br /&gt;
= Structure =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| 0x20&lt;br /&gt;
| SHA-256 hash over the raw data from offset 0x80 to *(code offset), exclusive&lt;br /&gt;
|-&lt;br /&gt;
| 0x20&lt;br /&gt;
| 0x20&lt;br /&gt;
| SHA-256 hash over the raw data from *(code offset) to *(module name offset), exclusive&lt;br /&gt;
|-&lt;br /&gt;
| 0x40&lt;br /&gt;
| 0x20&lt;br /&gt;
| SHA-256 hash over the raw data from *(module name offset) to *(data offset), exclusive&lt;br /&gt;
|-&lt;br /&gt;
| 0x60&lt;br /&gt;
| 0x20&lt;br /&gt;
| SHA-256 hash over the raw data from *(data offset) to *(data offset + data size), exclusive (RO does NOT check this hash)&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| 0x04&lt;br /&gt;
| Magic &amp;quot;CRO0&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| 0x04&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x88&lt;br /&gt;
| 0x04&lt;br /&gt;
| Next loaded CRO pointer, set by RO during loading (Usually zero when the CRO is being loaded)&lt;br /&gt;
|-&lt;br /&gt;
| 0x8C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Previous loaded CRO pointer, set by RO during loading&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| 0x04&lt;br /&gt;
| File size&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| 0x04&lt;br /&gt;
| .bss size&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| 0x04&lt;br /&gt;
| Fixed size, set by RO after fixing, used to keep track of the new size&lt;br /&gt;
|-&lt;br /&gt;
| 0x9C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 0xA0&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset|Segment offset]] that is always the same as export symbol &amp;quot;nnroControlObject_&amp;quot;. 0xFFFFFFFF in CRS&lt;br /&gt;
|-&lt;br /&gt;
| 0xA4&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset|Segment offset]] for &amp;quot;OnLoad&amp;quot; function, which will be called when the module is initialized. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xA8&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset|Segment offset]] for &amp;quot;OnExit&amp;quot; function, which will be called when the module is finalized. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xAC&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset|Segment offset]] for &amp;quot;OnUnresolved&amp;quot; function, which will be called when an unresolved function is called. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xB0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Code offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xB4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Code size&lt;br /&gt;
|-&lt;br /&gt;
| 0xB8&lt;br /&gt;
| 0x04&lt;br /&gt;
| .data offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xBC&lt;br /&gt;
| 0x04&lt;br /&gt;
| .data size&lt;br /&gt;
|-&lt;br /&gt;
| 0xC0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Module Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xC4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Module Name size&lt;br /&gt;
|-&lt;br /&gt;
| 0xC8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Segment Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xCC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Segment Table num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0xD0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Export Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xD4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Export Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0xD8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Export Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xDC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Export Table num (size = num * 4)&lt;br /&gt;
|-&lt;br /&gt;
| 0xE0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Strings offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xE4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Strings size&lt;br /&gt;
|-&lt;br /&gt;
| 0xE8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Trie offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xEC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Trie num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0xF0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Module Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xF4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Module Table num (size = num * 20)&lt;br /&gt;
|-&lt;br /&gt;
| 0xF8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Relocations offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xFC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Relocations num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0x100&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x104&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x108&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x10C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x110&lt;br /&gt;
| 0x04&lt;br /&gt;
| Anonymous Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x114&lt;br /&gt;
| 0x04&lt;br /&gt;
| Anonymous Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x118&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Strings offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x11C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Strings size&lt;br /&gt;
|-&lt;br /&gt;
| 0x120&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Fields 0x120, 0x130|Unknown Relocations]] base parameter offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x124&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Fields 0x120, 0x130|Unknown Relocations]] base parameter num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x128&lt;br /&gt;
| 0x04&lt;br /&gt;
| Internal Relocations offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x12C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Internal Relocations num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0x130&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Fields 0x120, 0x130|Unknown Relocations]] offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x134&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Fields 0x120, 0x130|Unknown Relocations]] num (size = num * 12)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Segment offset ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bits&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| Segment index for table&lt;br /&gt;
|-&lt;br /&gt;
| 4-31&lt;br /&gt;
| Offset into segment&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Segment Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment offset &lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment size&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment id (0 = .text, 1 = .rodata, 2 = .data, 3 = .bss)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Named Export Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for export&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Indexed Export Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for export&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Export Trie entry ==&lt;br /&gt;
&lt;br /&gt;
Each entry represents a node in the trie:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x2&lt;br /&gt;
| Flags (bits 0-2: bit index, bits 3-15: char index)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| 0x2&lt;br /&gt;
| Left node info (bit 0-14: node index, bit 15: is leaf)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x2&lt;br /&gt;
| Right node info (same as left node)&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x2&lt;br /&gt;
| Index for the named exports table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The root node is a dummy node, with the right child being a leaf and pointing to the empty string. Hence the search must be performed starting from the left child.&lt;br /&gt;
&lt;br /&gt;
Search is performed by extracting the bit of the input using informations encoded in the flags. If the bit is 0 or the offsets are out of bounds the search continues on the left, else on the right.&lt;br /&gt;
The search always yields a result, which may not match the input string, so a final strcmp call is required.&lt;br /&gt;
&lt;br /&gt;
== Named Import Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Indexed Import Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| index of the export symbol&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Anonymous Import Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] of the export symbol&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Import Module Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Module name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Indexed import num&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a sub list in Indexed Import Table&lt;br /&gt;
|-&lt;br /&gt;
| 0xC&lt;br /&gt;
| 0x4&lt;br /&gt;
| Anonymous import num&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a sub list in Anonymous Import Table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Relocation entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for output.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x1&lt;br /&gt;
| Relocation type (R_ARM_NONE = 0, R_ARM_ABS32 = 2, R_ARM_REL32 = 3, R_ARM_THM_PC22 = 10, R_ARM_CALL = 28, R_ARM_JUMP24 = 29, R_ARM_TARGET1 = 38, R_ARM_PREL31 = 42)&lt;br /&gt;
|-&lt;br /&gt;
| 0x5&lt;br /&gt;
| 0x1&lt;br /&gt;
| For import relocations, non-zero if last entry; for internal relocations, this is the referred segment index&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x1&lt;br /&gt;
| For import relocations, 1 is written to first entry if all symbols loaded successfully; unknown (padding?) for internal relocations&lt;br /&gt;
|-&lt;br /&gt;
| 0x7&lt;br /&gt;
| 0x1&lt;br /&gt;
| Unknown (padding?)&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| addend&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Relocation code from RO:&lt;br /&gt;
&lt;br /&gt;
 static Result writeRelocation(u32* out, u32 relocType, u32 addend, u32 base, u32 inputPtr) {&lt;br /&gt;
     const s32 branchOffset = inputPtr - base;&lt;br /&gt;
     u32 offset = base + addend - inputPtr;&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_NONE)&lt;br /&gt;
         return 0;&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_ABS32 || relocType == R_ARM_TARGET1) {&lt;br /&gt;
         *out = base + addend;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_REL32) {&lt;br /&gt;
         *out = offset;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_THM_PC22) {&lt;br /&gt;
         // +-4MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x400000 || branchOffset &amp;lt;= -0x400000)&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         if (base &amp;amp; 1) {&lt;br /&gt;
             *out = (((offset &amp;gt;&amp;gt; 12) | 0xF000) &amp;lt;&amp;lt; 16) | ((offset &amp;lt;&amp;lt; 4) &amp;gt;&amp;gt; 5) | 0xF800;&lt;br /&gt;
         } else {&lt;br /&gt;
             if (offset &amp;amp; 2)&lt;br /&gt;
                 offset += 2;&lt;br /&gt;
 &lt;br /&gt;
             *out = ((offset &amp;gt;&amp;gt; 12) | 0xF000) &amp;lt;&amp;lt; 16 | ((offset &amp;lt;&amp;lt; 4) &amp;gt;&amp;gt; 5) | 0xE800;&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_CALL) {&lt;br /&gt;
         // +-32MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x2000000 || branchOffset &amp;lt;= -0x2000000)&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         if (base &amp;amp; 1) {&lt;br /&gt;
             *out = ((offset &amp;lt;&amp;lt; 23) &amp;amp; 0x1000000) | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8) | 0xFA000000;&lt;br /&gt;
         } else {&lt;br /&gt;
             *out = 0xEB000000 | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8);&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_JUMP24) {&lt;br /&gt;
         // +-32MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x2000000 || branchOffset &amp;lt;= -0x2000000 || (base &amp;amp; 1))&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         *out = (*out &amp;amp; 0xFF000000) | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8);&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_PREL31) {&lt;br /&gt;
         *out = addend + ((base &amp;lt;&amp;lt; 1) &amp;gt;&amp;gt; 1) - inputPtr;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     return 0xD9012C22;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
== Fields 0x120, 0x130 ==&lt;br /&gt;
&lt;br /&gt;
Field 0x130 is a list of relocations which serve an unknown purpose. Field 0x120 is a list of entries related to the previous list:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset to a relocation entry in field 0x130&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment offset used for calculating the &amp;quot;base&amp;quot; parameter&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= nnroControlObject =&lt;br /&gt;
&lt;br /&gt;
Signature:&lt;br /&gt;
&lt;br /&gt;
 Result nnroControlObject(void* arg, u32 type);&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Type&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Write address of &amp;quot;nnroEitNode_&amp;quot; to memory pointed by arg (*(u32*)arg = &amp;amp;nnroEitNode_)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Set global in CRO to arg, where arg is the CRO end (g_CRO_end = (u32)arg)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Write CRO end to memory pointed by arg (*(u32*)arg = g_CRO_end)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Write begin, end of function list (C initializers?) to memory pointed by arg (*(u32*)arg = (u32)listBegin, ((u32*)arg)[1] = (u32)listEnd)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Write begin, end of function list (C++ initializers?) to memory pointed by arg (*(u32*)arg = (u32)listBegin, ((u32*)arg)[1] = (u32)listEnd)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;quot;EitNode&amp;quot; are nodes of a linked list containing informations about the main executable + loaded modules:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Type&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| EitNode*&lt;br /&gt;
| Pointer to the previous element of the list&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| EitNode*&lt;br /&gt;
| Pointer to the next element of the list&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| u32&lt;br /&gt;
| Module code start, for the main executable this is .text base, for CROs this is the value at CRO+0xB0&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| u32&lt;br /&gt;
| Module code end (start + size), for the main executable the size is the size of all mapped sections, for CROs it&#039;s the value at CRO+0xB4&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| u32*&lt;br /&gt;
| Unknown, seems to be an array of u32, probably holds debugging informations&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| u32*&lt;br /&gt;
| This points at the end of the previous array&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ElfNode* (?)&lt;br /&gt;
| Unknown, this is only set for the main executable node, and it points to itself&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=CRO0&amp;diff=23711</id>
		<title>CRO0</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=CRO0&amp;diff=23711"/>
		<updated>2025-12-26T11:08:18Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Trie entries + unknown fields&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:File formats]]&lt;br /&gt;
&lt;br /&gt;
CRO with extension .cro is used for &amp;quot;DLLs&amp;quot;. CRS with extension .crs is in the same format of CRO but storing the symbol information of the static module (the main application). The end of the file is aligned to a 0x1000-byte boundary with 0xCC bytes.&lt;br /&gt;
&lt;br /&gt;
The first hash-table entry hashes the 0x100-byte header following the hash-table. The following hash-table entries hash the sections specified in the header.&lt;br /&gt;
&lt;br /&gt;
When the RO module loads the entire CRO into process memory(mapped in the 0x00100000-0x04000000 region), it modifies the mapped CRO data. The magic field is also changed to &amp;quot;FIXD&amp;quot; if fix level is not 0.&lt;br /&gt;
&lt;br /&gt;
Upon loading, the RO module will look for export symbol &amp;quot;nnroAeabiAtexit_&amp;quot; to patch it to its import symbol &amp;quot;__aeabi_atexit&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
For dumping symbols and loading a CRO into IDA, see [https://github.com/plutooo/ctr/] and [https://github.com/wwylele/IDA_plugin_CRO].&lt;br /&gt;
&lt;br /&gt;
= Structure =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x80&lt;br /&gt;
| SHA-256 hash-table, verified by [[CRR0|CRR]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| 0x04&lt;br /&gt;
| Magic &amp;quot;CRO0&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| 0x04&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x88&lt;br /&gt;
| 0x04&lt;br /&gt;
| Next loaded CRO pointer, set by RO during loading (Usually zero when the CRO is being loaded)&lt;br /&gt;
|-&lt;br /&gt;
| 0x8C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Previous loaded CRO pointer, set by RO during loading&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| 0x04&lt;br /&gt;
| File size&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| 0x04&lt;br /&gt;
| .bss size&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| 0x04&lt;br /&gt;
| Fixed size, set by RO after fixing, used to keep track of the new size&lt;br /&gt;
|-&lt;br /&gt;
| 0x9C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 0xA0&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset|Segment offset]] that is always the same as export symbol &amp;quot;nnroControlObject_&amp;quot;. 0xFFFFFFFF in CRS&lt;br /&gt;
|-&lt;br /&gt;
| 0xA4&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset|Segment offset]] for &amp;quot;OnLoad&amp;quot; function, which will be called when the module is initialized. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xA8&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset|Segment offset]] for &amp;quot;OnExit&amp;quot; function, which will be called when the module is finalized. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xAC&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset|Segment offset]] for &amp;quot;OnUnresolved&amp;quot; function, which will be called when an unresolved function is called. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xB0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Code offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xB4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Code size&lt;br /&gt;
|-&lt;br /&gt;
| 0xB8&lt;br /&gt;
| 0x04&lt;br /&gt;
| .data offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xBC&lt;br /&gt;
| 0x04&lt;br /&gt;
| .data size&lt;br /&gt;
|-&lt;br /&gt;
| 0xC0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Module Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xC4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Module Name size&lt;br /&gt;
|-&lt;br /&gt;
| 0xC8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Segment Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xCC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Segment Table num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0xD0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Export Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xD4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Export Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0xD8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Export Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xDC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Export Table num (size = num * 4)&lt;br /&gt;
|-&lt;br /&gt;
| 0xE0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Strings offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xE4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Strings size&lt;br /&gt;
|-&lt;br /&gt;
| 0xE8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Trie offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xEC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Trie num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0xF0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Module Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xF4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Module Table num (size = num * 20)&lt;br /&gt;
|-&lt;br /&gt;
| 0xF8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Relocations offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xFC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Relocations num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0x100&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x104&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x108&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x10C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x110&lt;br /&gt;
| 0x04&lt;br /&gt;
| Anonymous Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x114&lt;br /&gt;
| 0x04&lt;br /&gt;
| Anonymous Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x118&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Strings offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x11C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Strings size&lt;br /&gt;
|-&lt;br /&gt;
| 0x120&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Fields 0x120, 0x130|Unknown Relocations]] base parameter offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x124&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Fields 0x120, 0x130|Unknown Relocations]] base parameter num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x128&lt;br /&gt;
| 0x04&lt;br /&gt;
| Internal Relocations offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x12C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Internal Relocations num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0x130&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Fields 0x120, 0x130|Unknown Relocations]] offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x134&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Fields 0x120, 0x130|Unknown Relocations]] num (size = num * 12)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Segment offset ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bits&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| Segment index for table&lt;br /&gt;
|-&lt;br /&gt;
| 4-31&lt;br /&gt;
| Offset into segment&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Segment Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment offset &lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment size&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment id (0 = .text, 1 = .rodata, 2 = .data, 3 = .bss)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Named Export Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for export&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Indexed Export Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for export&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Export Trie entry ==&lt;br /&gt;
&lt;br /&gt;
Each entry represents a node in the trie:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x2&lt;br /&gt;
| Flags (bits 0-2: bit index, bits 3-15: char index)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| 0x2&lt;br /&gt;
| Left node info (bit 0-14: node index, bit 15: is leaf)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x2&lt;br /&gt;
| Right node info (same as left node)&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x2&lt;br /&gt;
| Index for the named exports table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The root node is a dummy node, with the right child being a leaf and pointing to the empty string. Hence the search must be performed starting from the left child.&lt;br /&gt;
&lt;br /&gt;
Search is performed by extracting the bit of the input using informations encoded in the flags. If the bit is 0 or the offsets are out of bounds the search continues on the left, else on the right.&lt;br /&gt;
The search always yields a result, which may not match the input string, so a final strcmp call is required.&lt;br /&gt;
&lt;br /&gt;
== Named Import Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Indexed Import Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| index of the export symbol&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Anonymous Import Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] of the export symbol&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Import Module Table entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Module name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Indexed import num&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a sub list in Indexed Import Table&lt;br /&gt;
|-&lt;br /&gt;
| 0xC&lt;br /&gt;
| 0x4&lt;br /&gt;
| Anonymous import num&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a sub list in Anonymous Import Table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Relocation entry ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for output.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x1&lt;br /&gt;
| Relocation type (R_ARM_NONE = 0, R_ARM_ABS32 = 2, R_ARM_REL32 = 3, R_ARM_THM_PC22 = 10, R_ARM_CALL = 28, R_ARM_JUMP24 = 29, R_ARM_TARGET1 = 38, R_ARM_PREL31 = 42)&lt;br /&gt;
|-&lt;br /&gt;
| 0x5&lt;br /&gt;
| 0x1&lt;br /&gt;
| For import relocations, non-zero if last entry; for internal relocations, this is the referred segment index&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x1&lt;br /&gt;
| For import relocations, 1 is written to first entry if all symbols loaded successfully; unknown (padding?) for internal relocations&lt;br /&gt;
|-&lt;br /&gt;
| 0x7&lt;br /&gt;
| 0x1&lt;br /&gt;
| Unknown (padding?)&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| addend&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Relocation code from RO:&lt;br /&gt;
&lt;br /&gt;
 static Result writeRelocation(u32* out, u32 relocType, u32 addend, u32 base, u32 inputPtr) {&lt;br /&gt;
     const s32 branchOffset = inputPtr - base;&lt;br /&gt;
     u32 offset = base + addend - inputPtr;&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_NONE)&lt;br /&gt;
         return 0;&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_ABS32 || relocType == R_ARM_TARGET1) {&lt;br /&gt;
         *out = base + addend;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_REL32) {&lt;br /&gt;
         *out = offset;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_THM_PC22) {&lt;br /&gt;
         // +-4MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x400000 || branchOffset &amp;lt;= -0x400000)&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         if (base &amp;amp; 1) {&lt;br /&gt;
             *out = (((offset &amp;gt;&amp;gt; 12) | 0xF000) &amp;lt;&amp;lt; 16) | ((offset &amp;lt;&amp;lt; 4) &amp;gt;&amp;gt; 5) | 0xF800;&lt;br /&gt;
         } else {&lt;br /&gt;
             if (offset &amp;amp; 2)&lt;br /&gt;
                 offset += 2;&lt;br /&gt;
 &lt;br /&gt;
             *out = ((offset &amp;gt;&amp;gt; 12) | 0xF000) &amp;lt;&amp;lt; 16 | ((offset &amp;lt;&amp;lt; 4) &amp;gt;&amp;gt; 5) | 0xE800;&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_CALL) {&lt;br /&gt;
         // +-32MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x2000000 || branchOffset &amp;lt;= -0x2000000)&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         if (base &amp;amp; 1) {&lt;br /&gt;
             *out = ((offset &amp;lt;&amp;lt; 23) &amp;amp; 0x1000000) | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8) | 0xFA000000;&lt;br /&gt;
         } else {&lt;br /&gt;
             *out = 0xEB000000 | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8);&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_JUMP24) {&lt;br /&gt;
         // +-32MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x2000000 || branchOffset &amp;lt;= -0x2000000 || (base &amp;amp; 1))&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         *out = (*out &amp;amp; 0xFF000000) | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8);&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (relocType == R_ARM_PREL31) {&lt;br /&gt;
         *out = addend + ((base &amp;lt;&amp;lt; 1) &amp;gt;&amp;gt; 1) - inputPtr;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     return 0xD9012C22;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
== Fields 0x120, 0x130 ==&lt;br /&gt;
&lt;br /&gt;
Field 0x130 is a list of relocations which serve an unknown purpose. Field 0x120 is a list of entries related to the previous list:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset to a relocation entry in field 0x130&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment offset used for calculating the &amp;quot;base&amp;quot; parameter&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= nnroControlObject =&lt;br /&gt;
&lt;br /&gt;
Signature:&lt;br /&gt;
&lt;br /&gt;
 Result nnroControlObject(void* arg, u32 type);&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Type&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Write address of &amp;quot;nnroEitNode_&amp;quot; to memory pointed by arg (*(u32*)arg = &amp;amp;nnroEitNode_)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Set global in CRO to arg, where arg is the CRO end (g_CRO_end = (u32)arg)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Write CRO end to memory pointed by arg (*(u32*)arg = g_CRO_end)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Write begin, end of function list (C initializers?) to memory pointed by arg (*(u32*)arg = (u32)listBegin, ((u32*)arg)[1] = (u32)listEnd)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Write begin, end of function list (C++ initializers?) to memory pointed by arg (*(u32*)arg = (u32)listBegin, ((u32*)arg)[1] = (u32)listEnd)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;quot;EitNode&amp;quot; are nodes of a linked list containing informations about the main executable + loaded modules:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Type&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| EitNode*&lt;br /&gt;
| Pointer to the previous element of the list&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| EitNode*&lt;br /&gt;
| Pointer to the next element of the list&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| u32&lt;br /&gt;
| Module code start, for the main executable this is .text base, for CROs this is the value at CRO+0xB0&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| u32&lt;br /&gt;
| Module code end (start + size), for the main executable the size is the size of all mapped sections, for CROs it&#039;s the value at CRO+0xB4&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| u32*&lt;br /&gt;
| Unknown, seems to be an array of u32, probably holds debugging informations&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| u32*&lt;br /&gt;
| This points at the end of the previous array&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ElfNode* (?)&lt;br /&gt;
| Unknown, this is only set for the main executable node, and it points to itself&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=Thread_Local_Storage&amp;diff=23707</id>
		<title>Thread Local Storage</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=Thread_Local_Storage&amp;diff=23707"/>
		<updated>2025-11-25T22:45:37Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Fix sizes&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Each thread is given a 0x200-byte thread-local-storage block by the kernel. It is used during [[IPC_Command_Structure|IPC communications]]. Each [[KProcess]] has a linked list of [[KThreadLocalPage]] objects and each object has a pointer to a page(0x1000 bytes) of FCRAM, which is used for up to 8 thread-local-storage blocks. The [[KThreadLocalPage]] is used to track which thread-local-storage blocks are used so it can allocate new ones as new threads are created. &lt;br /&gt;
&lt;br /&gt;
A pointer to the thread-local-storage can be read using the ARM instruction:&lt;br /&gt;
 mrc        15, 0, rX, cr13, cr0, 3&lt;br /&gt;
&lt;br /&gt;
Every application has RW access to its thread-local-storage.&lt;br /&gt;
&lt;br /&gt;
The kernel requires the application to put IPC parameters and IPC static buffers inside the thread-local-storage:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| 0x40&lt;br /&gt;
| Undefined, can be used by application for any purpose&lt;br /&gt;
|-&lt;br /&gt;
| 0x40&lt;br /&gt;
| 0x04&lt;br /&gt;
| Exception handler return address&lt;br /&gt;
|-&lt;br /&gt;
| 0x44&lt;br /&gt;
| 0x04&lt;br /&gt;
| Exception handler SP control (1 = use aligned SP, other = use arbitrary memory, must be mapped &amp;amp; RW)&lt;br /&gt;
|-&lt;br /&gt;
| 0x48&lt;br /&gt;
| 0x04&lt;br /&gt;
| Exception handler [[ERR:Throw#Exception|context]] control (0 = use SP control memory, 1 = use aligned SP, other = use arbitrary memory, must be mapped &amp;amp; RW)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4C&lt;br /&gt;
| 0x34&lt;br /&gt;
| Undefined&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| 0x100&lt;br /&gt;
| IPC command with parameters&lt;br /&gt;
|-&lt;br /&gt;
| 0x180&lt;br /&gt;
| 0x80&lt;br /&gt;
| IPC static buffers: 16 pairs of a [[IPC|static buffer translation descriptor]] (specifying the buffer size) and a buffer pointer&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=CRO0&amp;diff=23659</id>
		<title>CRO0</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=CRO0&amp;diff=23659"/>
		<updated>2025-08-15T19:53:17Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:File formats]]&lt;br /&gt;
&lt;br /&gt;
CRO with extension .cro is used for &amp;quot;DLLs&amp;quot;. CRS with extension .crs is in the same format of CRO but storing the symbol information of the static module (the main application). The end of the file is aligned to a 0x1000-byte boundary with 0xCC bytes.&lt;br /&gt;
&lt;br /&gt;
The first hash-table entry hashes the 0x100-byte header following the hash-table. The following hash-table entries hash the sections specified in the header.&lt;br /&gt;
&lt;br /&gt;
When the RO module loads the entire CRO into process memory(mapped in the 0x00100000-0x04000000 region), it modifies the mapped CRO data. The magic field is also changed to &amp;quot;FIXD&amp;quot; if fix level is not 0.&lt;br /&gt;
&lt;br /&gt;
Upon loading, the RO module will look for export symbol &amp;quot;nnroAeabiAtexit_&amp;quot; to patch it to its import symbol &amp;quot;__aeabi_atexit&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
For dumping symbols and loading a CRO into IDA, see [https://github.com/plutooo/ctr/] and [https://github.com/wwylele/IDA_plugin_CRO].&lt;br /&gt;
&lt;br /&gt;
= Structure =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x80&lt;br /&gt;
| SHA-256 hash-table, verified by [[CRR0|CRR]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| 0x04&lt;br /&gt;
| Magic &amp;quot;CRO0&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| 0x04&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x88&lt;br /&gt;
| 0x04&lt;br /&gt;
| Next loaded CRO pointer, set by RO during loading (Usually zero when the CRO is being loaded)&lt;br /&gt;
|-&lt;br /&gt;
| 0x8C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Previous loaded CRO pointer, set by RO during loading&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| 0x04&lt;br /&gt;
| File size&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| 0x04&lt;br /&gt;
| .bss size&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| 0x04&lt;br /&gt;
| Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 0x9C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 0xA0&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] that is always the same as export symbol &amp;quot;nnroControlObject_&amp;quot;. 0xFFFFFFFF in CRS&lt;br /&gt;
|-&lt;br /&gt;
| 0xA4&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for &amp;quot;OnLoad&amp;quot; function, which will be called when the module is initialized. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xA8&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for &amp;quot;OnExit&amp;quot; function, which will be called when the module is finalized. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xAC&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for &amp;quot;OnUnresolved&amp;quot; function, which will be called when an unresolved function is called. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xB0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Code offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xB4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Code size&lt;br /&gt;
|-&lt;br /&gt;
| 0xB8&lt;br /&gt;
| 0x04&lt;br /&gt;
| .data offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xBC&lt;br /&gt;
| 0x04&lt;br /&gt;
| .data size&lt;br /&gt;
|-&lt;br /&gt;
| 0xC0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Module Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xC4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Module Name size&lt;br /&gt;
|-&lt;br /&gt;
| 0xC8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Segment Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xCC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Segment Table num (size = num*12)&lt;br /&gt;
|-&lt;br /&gt;
| 0xD0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Export Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xD4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Export Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0xD8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Export Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xDC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Export Table num (size = num * 4)&lt;br /&gt;
|-&lt;br /&gt;
| 0xE0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Strings offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xE4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Strings size&lt;br /&gt;
|-&lt;br /&gt;
| 0xE8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Tree offset (fast lookups based on a trie-like structure)&lt;br /&gt;
|-&lt;br /&gt;
| 0xEC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Tree num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0xF0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Module Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xF4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Module Table num (size = num * 20)&lt;br /&gt;
|-&lt;br /&gt;
| 0xF8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Patches offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xFC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Patches num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0x100&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x104&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x108&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x10C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x110&lt;br /&gt;
| 0x04&lt;br /&gt;
| Anonymous Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x114&lt;br /&gt;
| 0x04&lt;br /&gt;
| Anonymous Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x118&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Strings offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x11C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Strings size&lt;br /&gt;
|-&lt;br /&gt;
| 0x120&lt;br /&gt;
| 0x04&lt;br /&gt;
| unk8 offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x124&lt;br /&gt;
| 0x04&lt;br /&gt;
| unk8 num&lt;br /&gt;
|-&lt;br /&gt;
| 0x128&lt;br /&gt;
| 0x04&lt;br /&gt;
| Relocation Patches offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x12C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Relocation Patches num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0x130&lt;br /&gt;
| 0x04&lt;br /&gt;
| unk9 offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x134&lt;br /&gt;
| 0x04&lt;br /&gt;
| unk9 num&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Segment offset (4 bytes)==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bits&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| Segment index for table&lt;br /&gt;
|-&lt;br /&gt;
| 4-31&lt;br /&gt;
| Offset into segment&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Segment Table entry (12 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment offset &lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment size&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment id (0 = .text, 1 = .rodata, 2 = .data, 3 = .bss)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Named Export Table entry (8 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for export&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Indexed Export Table entry (4 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for export&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Named Import Table entry (8 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Indexed Import Table entry (8 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| index of the export symbol&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Anonymous Import Table entry (8 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] of the export symbol&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Import Module Table entry (20 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Module name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Indexed import num&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a sub list in Indexed Import Table&lt;br /&gt;
|-&lt;br /&gt;
| 0xC&lt;br /&gt;
| 0x4&lt;br /&gt;
| Anonymous import num&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a sub list in Anonymous Import Table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Patch entry (12 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for output.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x1&lt;br /&gt;
| Patch type (R_ARM_NONE = 0, R_ARM_ABS32 = 2, R_ARM_REL32 = 3, R_ARM_THM_PC22 = 10, R_ARM_CALL = 28, R_ARM_JUMP24 = 29, R_ARM_TARGET1 = 38, R_ARM_PREL31 = 42)&lt;br /&gt;
|-&lt;br /&gt;
| 0x5&lt;br /&gt;
| 0x1&lt;br /&gt;
| For import patches, non-zero if last entry; for relocation patches, this is the referred segment index&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x1&lt;br /&gt;
| For import patches, 1 is written to first entry if all symbols loaded successfully; unknown (padding?) for relocation patches&lt;br /&gt;
|-&lt;br /&gt;
| 0x7&lt;br /&gt;
| 0x1&lt;br /&gt;
| Unknown (padding?)&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| addend&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Relocation code from RO:&lt;br /&gt;
&lt;br /&gt;
 static Result writePatch(u32* out, u32 patchType, u32 addend, u32 base, u32 inputPtr) {&lt;br /&gt;
     const s32 branchOffset = inputPtr - base;&lt;br /&gt;
     u32 offset = base + addend - inputPtr;&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_NONE)&lt;br /&gt;
         return 0;&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_ABS32 || patchType == R_ARM_TARGET1) {&lt;br /&gt;
         *out = base + addend;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_REL32) {&lt;br /&gt;
         *out = offset;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_THM_PC22) {&lt;br /&gt;
         // +-4MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x400000 || branchOffset &amp;lt;= -0x400000)&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         if (base &amp;amp; 1) {&lt;br /&gt;
             *out = (((offset &amp;gt;&amp;gt; 12) | 0xF000) &amp;lt;&amp;lt; 16) | ((offset &amp;lt;&amp;lt; 4) &amp;gt;&amp;gt; 5) | 0xF800;&lt;br /&gt;
         } else {&lt;br /&gt;
             if (offset &amp;amp; 2)&lt;br /&gt;
                 offset += 2;&lt;br /&gt;
 &lt;br /&gt;
             *out = ((offset &amp;gt;&amp;gt; 12) | 0xF000) &amp;lt;&amp;lt; 16 | ((offset &amp;lt;&amp;lt; 4) &amp;gt;&amp;gt; 5) | 0xE800;&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_CALL) {&lt;br /&gt;
         // +-32MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x2000000 || branchOffset &amp;lt;= -0x2000000)&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         if (base &amp;amp; 1) {&lt;br /&gt;
             *out = ((offset &amp;lt;&amp;lt; 23) &amp;amp; 0x1000000) | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8) | 0xFA000000;&lt;br /&gt;
         } else {&lt;br /&gt;
             *out = 0xEB000000 | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8);&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_JUMP24) {&lt;br /&gt;
         // +-32MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x2000000 || branchOffset &amp;lt;= -0x2000000 || (base &amp;amp; 1))&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         *out = (*out &amp;amp; 0xFF000000) | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8);&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_PREL31) {&lt;br /&gt;
         *out = addend + ((base &amp;lt;&amp;lt; 1) &amp;gt;&amp;gt; 1) - inputPtr;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     return 0xD9012C22;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
== nnroControlObject ==&lt;br /&gt;
&lt;br /&gt;
Signature:&lt;br /&gt;
&lt;br /&gt;
 Result nnroControlObject(void* arg, u32 type);&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Type&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Write address of &amp;quot;nnroEitNode_&amp;quot; to memory pointed by arg (*(u32*)arg = &amp;amp;nnroEitNode_)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Set global in CRO to arg, where arg is the CRO end (g_CRO_end = (u32)arg)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Write CRO end to memory pointed by arg (*(u32*)arg = g_CRO_end)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Write begin, end of function list (C initializers?) to memory pointed by arg (*(u32*)arg = (u32)listBegin, ((u32*)arg)[1] = (u32)listEnd)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Write begin, end of function list (C++ initializers?) to memory pointed by arg (*(u32*)arg = (u32)listBegin, ((u32*)arg)[1] = (u32)listEnd)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;quot;EitNode&amp;quot; are nodes of a linked list containing informations about the main executable + loaded modules:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Type&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| EitNode*&lt;br /&gt;
| Pointer to the previous element of the list&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| EitNode*&lt;br /&gt;
| Pointer to the next element of the list&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| u32&lt;br /&gt;
| Module code start, for the main executable this is .text base, for CROs this is the value at CRO+0xB0&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| u32&lt;br /&gt;
| Module code end (start + size), for the main executable the size is the size of all mapped sections, for CROs it&#039;s the value at CRO+0xB4&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| u32*&lt;br /&gt;
| Unknown, seems to be an array of u32, probably holds debugging informations&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| u32*&lt;br /&gt;
| This points at the end of the previous array&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ElfNode* (?)&lt;br /&gt;
| Unknown, this is only set for the main executable node, and it points to itself&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=CRO0&amp;diff=23658</id>
		<title>CRO0</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=CRO0&amp;diff=23658"/>
		<updated>2025-08-15T19:51:24Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: nnroControlObject&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:File formats]]&lt;br /&gt;
&lt;br /&gt;
CRO with extension .cro is used for &amp;quot;DLLs&amp;quot;. CRS with extension .crs is in the same format of CRO but storing the symbol information of the static module (the main application). The end of the file is aligned to a 0x1000-byte boundary with 0xCC bytes.&lt;br /&gt;
&lt;br /&gt;
The first hash-table entry hashes the 0x100-byte header following the hash-table. The following hash-table entries hash the sections specified in the header.&lt;br /&gt;
&lt;br /&gt;
When the RO module loads the entire CRO into process memory(mapped in the 0x00100000-0x04000000 region), it modifies the mapped CRO data. The magic field is also changed to &amp;quot;FIXD&amp;quot; if fix level is not 0.&lt;br /&gt;
&lt;br /&gt;
Upon loading, the RO module will look for export symbol &amp;quot;nnroAeabiAtexit_&amp;quot; to patch it to its import symbol &amp;quot;__aeabi_atexit&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
For dumping symbols and loading a CRO into IDA, see [https://github.com/plutooo/ctr/] and [https://github.com/wwylele/IDA_plugin_CRO].&lt;br /&gt;
&lt;br /&gt;
= Structure =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x80&lt;br /&gt;
| SHA-256 hash-table, verified by [[CRR0|CRR]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| 0x04&lt;br /&gt;
| Magic &amp;quot;CRO0&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| 0x04&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x88&lt;br /&gt;
| 0x04&lt;br /&gt;
| Next loaded CRO pointer, set by RO during loading (Usually zero when the CRO is being loaded)&lt;br /&gt;
|-&lt;br /&gt;
| 0x8C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Previous loaded CRO pointer, set by RO during loading&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| 0x04&lt;br /&gt;
| File size&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| 0x04&lt;br /&gt;
| .bss size&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| 0x04&lt;br /&gt;
| Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 0x9C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 0xA0&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] that is always the same as export symbol &amp;quot;nnroControlObject_&amp;quot;. 0xFFFFFFFF in CRS&lt;br /&gt;
|-&lt;br /&gt;
| 0xA4&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for &amp;quot;OnLoad&amp;quot; function, which will be called when the module is initialized. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xA8&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for &amp;quot;OnExit&amp;quot; function, which will be called when the module is finalized. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xAC&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for &amp;quot;OnUnresolved&amp;quot; function, which will be called when an unresolved function is called. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xB0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Code offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xB4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Code size&lt;br /&gt;
|-&lt;br /&gt;
| 0xB8&lt;br /&gt;
| 0x04&lt;br /&gt;
| .data offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xBC&lt;br /&gt;
| 0x04&lt;br /&gt;
| .data size&lt;br /&gt;
|-&lt;br /&gt;
| 0xC0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Module Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xC4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Module Name size&lt;br /&gt;
|-&lt;br /&gt;
| 0xC8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Segment Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xCC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Segment Table num (size = num*12)&lt;br /&gt;
|-&lt;br /&gt;
| 0xD0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Export Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xD4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Export Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0xD8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Export Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xDC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Export Table num (size = num * 4)&lt;br /&gt;
|-&lt;br /&gt;
| 0xE0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Strings offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xE4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Strings size&lt;br /&gt;
|-&lt;br /&gt;
| 0xE8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Tree offset (fast lookups based on a trie-like structure)&lt;br /&gt;
|-&lt;br /&gt;
| 0xEC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Tree num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0xF0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Module Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xF4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Module Table num (size = num * 20)&lt;br /&gt;
|-&lt;br /&gt;
| 0xF8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Patches offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xFC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Patches num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0x100&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x104&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x108&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x10C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x110&lt;br /&gt;
| 0x04&lt;br /&gt;
| Anonymous Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x114&lt;br /&gt;
| 0x04&lt;br /&gt;
| Anonymous Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x118&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Strings offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x11C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Strings size&lt;br /&gt;
|-&lt;br /&gt;
| 0x120&lt;br /&gt;
| 0x04&lt;br /&gt;
| unk8 offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x124&lt;br /&gt;
| 0x04&lt;br /&gt;
| unk8 num&lt;br /&gt;
|-&lt;br /&gt;
| 0x128&lt;br /&gt;
| 0x04&lt;br /&gt;
| Relocation Patches offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x12C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Relocation Patches num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0x130&lt;br /&gt;
| 0x04&lt;br /&gt;
| unk9 offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x134&lt;br /&gt;
| 0x04&lt;br /&gt;
| unk9 num&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Segment offset (4 bytes)==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bits&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| Segment index for table&lt;br /&gt;
|-&lt;br /&gt;
| 4-31&lt;br /&gt;
| Offset into segment&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Segment Table entry (12 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment offset &lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment size&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment id (0 = .text, 1 = .rodata, 2 = .data, 3 = .bss)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Named Export Table entry (8 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for export&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Indexed Export Table entry (4 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for export&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Named Import Table entry (8 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Indexed Import Table entry (8 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| index of the export symbol&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Anonymous Import Table entry (8 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] of the export symbol&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Import Module Table entry (20 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Module name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Indexed import num&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a sub list in Indexed Import Table&lt;br /&gt;
|-&lt;br /&gt;
| 0xC&lt;br /&gt;
| 0x4&lt;br /&gt;
| Anonymous import num&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a sub list in Anonymous Import Table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Patch entry (12 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for output.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x1&lt;br /&gt;
| Patch type (R_ARM_NONE = 0, R_ARM_ABS32 = 2, R_ARM_REL32 = 3, R_ARM_THM_PC22 = 10, R_ARM_CALL = 28, R_ARM_JUMP24 = 29, R_ARM_TARGET1 = 38, R_ARM_PREL31 = 42)&lt;br /&gt;
|-&lt;br /&gt;
| 0x5&lt;br /&gt;
| 0x1&lt;br /&gt;
| For import patches, non-zero if last entry; for relocation patches, this is the referred segment index&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x1&lt;br /&gt;
| For import patches, 1 is written to first entry if all symbols loaded successfully; unknown (padding?) for relocation patches&lt;br /&gt;
|-&lt;br /&gt;
| 0x7&lt;br /&gt;
| 0x1&lt;br /&gt;
| Unknown (padding?)&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| addend&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Relocation code from RO:&lt;br /&gt;
&lt;br /&gt;
 static Result writePatch(u32* out, u32 patchType, u32 addend, u32 base, u32 inputPtr) {&lt;br /&gt;
     const s32 branchOffset = inputPtr - base;&lt;br /&gt;
     u32 offset = base + addend - inputPtr;&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_NONE)&lt;br /&gt;
         return 0;&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_ABS32 || patchType == R_ARM_TARGET1) {&lt;br /&gt;
         *out = base + addend;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_REL32) {&lt;br /&gt;
         *out = offset;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_THM_PC22) {&lt;br /&gt;
         // +-4MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x400000 || branchOffset &amp;lt;= -0x400000)&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         if (base &amp;amp; 1) {&lt;br /&gt;
             *out = (((offset &amp;gt;&amp;gt; 12) | 0xF000) &amp;lt;&amp;lt; 16) | ((offset &amp;lt;&amp;lt; 4) &amp;gt;&amp;gt; 5) | 0xF800;&lt;br /&gt;
         } else {&lt;br /&gt;
             if (offset &amp;amp; 2)&lt;br /&gt;
                 offset += 2;&lt;br /&gt;
 &lt;br /&gt;
             *out = ((offset &amp;gt;&amp;gt; 12) | 0xF000) &amp;lt;&amp;lt; 16 | ((offset &amp;lt;&amp;lt; 4) &amp;gt;&amp;gt; 5) | 0xE800;&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_CALL) {&lt;br /&gt;
         // +-32MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x2000000 || branchOffset &amp;lt;= -0x2000000)&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         if (base &amp;amp; 1) {&lt;br /&gt;
             *out = ((offset &amp;lt;&amp;lt; 23) &amp;amp; 0x1000000) | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8) | 0xFA000000;&lt;br /&gt;
         } else {&lt;br /&gt;
             *out = 0xEB000000 | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8);&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_JUMP24) {&lt;br /&gt;
         // +-32MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x2000000 || branchOffset &amp;lt;= -0x2000000 || (base &amp;amp; 1))&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         *out = (*out &amp;amp; 0xFF000000) | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8);&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_PREL31) {&lt;br /&gt;
         *out = addend + ((base &amp;lt;&amp;lt; 1) &amp;gt;&amp;gt; 1) - inputPtr;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     return 0xD9012C22;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
== nnroControlObject ==&lt;br /&gt;
&lt;br /&gt;
Signature:&lt;br /&gt;
&lt;br /&gt;
 Result nnroControlObject(void* arg, u32 type);&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Type&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Write address of &amp;quot;nnroEitNode_&amp;quot; to memory pointed by arg (*(u32*)p = &amp;amp;nnroEitNode_)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Set global in CRO to arg, where arg is the CRO end (g_CRO_end = p)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Write CRO end to memory pointed by arg (*(u32*)p = g_CRO_end)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Write begin, end of function list (C initializers?) to memory pointed by arg (*(u32*)p = listBegin, ((u32*)p)[1] = listEnd)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Write begin, end of function list (C++ initializers?) to memory pointed by arg (*(u32*)p = listBegin, ((u32*)p)[1] = listEnd)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;quot;EitNode&amp;quot; are nodes of a linked list containing informations about the main executable + loaded modules:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Type&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| EitNode*&lt;br /&gt;
| Pointer to the previous element of the list&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| EitNode*&lt;br /&gt;
| Pointer to the next element of the list&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| u32&lt;br /&gt;
| Module code start, for the main executable this is .text base, for CROs this is the value at CRO+0xB0&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| u32&lt;br /&gt;
| Module code end (start + size), for the main executable the size is the size of all mapped sections, for CROs it&#039;s the value at CRO+0xB4&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| u32*&lt;br /&gt;
| Unknown, seems to be an array of u32, probably holds debugging informations&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| u32*&lt;br /&gt;
| This points at the end of the previous array&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ElfNode* (?)&lt;br /&gt;
| Unknown, this is only set for the main executable node, and it points to itself&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GPU/Internal_Registers&amp;diff=23648</id>
		<title>GPU/Internal Registers</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GPU/Internal_Registers&amp;diff=23648"/>
		<updated>2025-08-03T08:58:13Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Texunit addresses order&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:GPU]]&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
GPU internal registers are written to through GPU commands. They are used to control the GPU&#039;s behavior, that is to say tell it to draw stuff and how we want it drawn.&lt;br /&gt;
&lt;br /&gt;
Each command is at least 8 bytes wide. The first word is the command parameter and the second word constitutes the command header. Optionally, more parameter words may follow (potentially including a padding word to align commands to multiples of 8 bytes).&lt;br /&gt;
&lt;br /&gt;
In the simplest case, a command is exactly 8 bytes wide. You can think of such a command as writing the parameter word to an internal register (the index of which is given in the command header). The more general case where more than one parameter word is given is equivalent to multiple simple commands (one for each parameter word). If consecutive writing mode is enabled in the command header, the current command index will be incremented after each parameter write. Otherwise, the parameters will be consecutively written to the same register.&lt;br /&gt;
&lt;br /&gt;
For example, the sequence &amp;quot;0xAAAAAAAA 0x802F011C 0xBBBBBBBB 0xCCCCCCCC&amp;quot; is equivalent to a call to commands 0xF011C with parameter 0xAAAAAAAA, 0xF011D with parameter 0xBBBBBBBB and 0xF011E with parameter 0xCCCCCCCC. If consecutive writing mode were disabled, the command would be equivalent to three consecutive calls to 0xF011C (once with parameter 0xAAAAAAAA, once with 0xBBBBBBBB, and finally with 0xCCCCCCCC).&lt;br /&gt;
&lt;br /&gt;
Invalid GPU command parameters including NaN floats can cause the GPU to hang, which then causes the GSP module to hang as well.&lt;br /&gt;
&lt;br /&gt;
The size of GPU command buffers must be 0x10-byte aligned; the lower 3 bits of the size are cleared. A common pitfall is having the finalization command (write to register 0x0010) not executed because it was the last 8 bytes of a non-0x10 byte aligned command buffer, and having the GPU hang as a result.&lt;br /&gt;
&lt;br /&gt;
=== Command Header ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-15&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 16-19&lt;br /&gt;
| Parameter mask&lt;br /&gt;
|-&lt;br /&gt;
| 20-27&lt;br /&gt;
| Number of extra parameters (may be zero)&lt;br /&gt;
|-&lt;br /&gt;
| 28-30&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 31&lt;br /&gt;
| Consecutive writing mode&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Parameter masking ===&lt;br /&gt;
&lt;br /&gt;
Using a value other than 0xF, parts of a word in internal GPU memory can be updated without touching the other bits of it. For example, setting bit 16 to zero indicates that the least significant byte of the parameter will not be overwritten, setting bit 17 to zero indicates that the parameter&#039;s second LSB will not be overwritten, etc. This means that for instance commands 0x00010107 and 0x00020107 refer to the same thing but write different parts of the parameter.&lt;br /&gt;
&lt;br /&gt;
=== Types ===&lt;br /&gt;
&lt;br /&gt;
There are three main types of registers :&lt;br /&gt;
* configuration registers, which directly map to various rendering properties (for example: [[#GPUREG_FACECULLING_CONFIG|GPUREG_FACECULLING_CONFIG]])&lt;br /&gt;
* data transfer registers, which can be seen as FIFOs that let us send sequential chunks of data to the GPU, such as shader code or 1D samplers (for example: [[#GPUREG_SH_CODETRANSFER_DATA|GPUREG_GSH_CODETRANSFER_DATA]])&lt;br /&gt;
* action triggering registers, which tell the GPU to do something, like draw a primitive (for example: [[#GPUREG_DRAWARRAYS|GPUREG_DRAWARRAYS]])&lt;br /&gt;
&lt;br /&gt;
=== Aliases ===&lt;br /&gt;
&lt;br /&gt;
It is possible for multiple register (sequential) IDs to correspond to the same register. This is done to leverage the consecutive writing mode for GPU commands, which makes it possible for a single command to write data to multiple sequential register IDs. For example, register IDs 02C1 through 02C8 all correspond to [[#GPUREG_VSH_FLOATUNIFORM_DATAi|GPUREG_VSH_FLOATUNIFORM_DATA&#039;&#039;i&#039;&#039;]] so that a consecutively writing command based at 02C0 will write its first parameter to [[#GPUREG_VSH_FLOATUNIFORM_INDEX|GPUREG_VSH_FLOATUNIFORM_INDEX]] and ever subsequent ones to [[#GPUREG_VSH_FLOATUNIFORM_DATAi|GPUREG_VSH_FLOATUNIFORM_DATA&#039;&#039;i&#039;&#039;]]&lt;br /&gt;
&lt;br /&gt;
=== Data Types ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Name&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| signed&lt;br /&gt;
| Signed integer&lt;br /&gt;
|-&lt;br /&gt;
| unsigned&lt;br /&gt;
| Unsigned integer&lt;br /&gt;
|-&lt;br /&gt;
| floatX.Y.Z&lt;br /&gt;
| Floating-point number with X sign bits, Y exponent bits, and Z mantissa bits&lt;br /&gt;
|-&lt;br /&gt;
| fixedX.Y.Z&lt;br /&gt;
| Fixed-point number with X sign bits, Y integer bits, and Z fractional bits&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Register list ==&lt;br /&gt;
&lt;br /&gt;
=== Miscellaneous registers (0x000-0x03F) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Register ID&lt;br /&gt;
! Register name&lt;br /&gt;
! Notes&lt;br /&gt;
! Official Name&lt;br /&gt;
|-&lt;br /&gt;
| 0000&lt;br /&gt;
| [[#GPUREG_0000|GPUREG_0000]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0001&lt;br /&gt;
| [[#GPUREG_0001|GPUREG_0001]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0002&lt;br /&gt;
| [[#GPUREG_0002|GPUREG_0002]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0003&lt;br /&gt;
| [[#GPUREG_0003|GPUREG_0003]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0004&lt;br /&gt;
| [[#GPUREG_0004|GPUREG_0004]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0005&lt;br /&gt;
| [[#GPUREG_0005|GPUREG_0005]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0006&lt;br /&gt;
| [[#GPUREG_0006|GPUREG_0006]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0007&lt;br /&gt;
| [[#GPUREG_0007|GPUREG_0007]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0008&lt;br /&gt;
| [[#GPUREG_0008|GPUREG_0008]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0009&lt;br /&gt;
| [[#GPUREG_0009|GPUREG_0009]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 000A&lt;br /&gt;
| [[#GPUREG_000A|GPUREG_000A]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 000B&lt;br /&gt;
| [[#GPUREG_000B|GPUREG_000B]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 000C&lt;br /&gt;
| [[#GPUREG_000C|GPUREG_000C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 000D&lt;br /&gt;
| [[#GPUREG_000D|GPUREG_000D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 000E&lt;br /&gt;
| [[#GPUREG_000E|GPUREG_000E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 000F&lt;br /&gt;
| [[#GPUREG_000F|GPUREG_000F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0010&lt;br /&gt;
| [[#GPUREG_FINALIZE|GPUREG_FINALIZE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_INTERRUPT&lt;br /&gt;
|-&lt;br /&gt;
| 0011&lt;br /&gt;
| [[#GPUREG_0011|GPUREG_0011]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0012&lt;br /&gt;
| [[#GPUREG_0012|GPUREG_0012]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0013&lt;br /&gt;
| [[#GPUREG_0013|GPUREG_0013]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0014&lt;br /&gt;
| [[#GPUREG_0014|GPUREG_0014]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0015&lt;br /&gt;
| [[#GPUREG_0015|GPUREG_0015]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0016&lt;br /&gt;
| [[#GPUREG_0016|GPUREG_0016]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0017&lt;br /&gt;
| [[#GPUREG_0017|GPUREG_0017]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0018&lt;br /&gt;
| [[#GPUREG_0018|GPUREG_0018]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0019&lt;br /&gt;
| [[#GPUREG_0019|GPUREG_0019]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 001A&lt;br /&gt;
| [[#GPUREG_001A|GPUREG_001A]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 001B&lt;br /&gt;
| [[#GPUREG_001B|GPUREG_001B]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 001C&lt;br /&gt;
| [[#GPUREG_001C|GPUREG_001C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 001D&lt;br /&gt;
| [[#GPUREG_001D|GPUREG_001D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 001E&lt;br /&gt;
| [[#GPUREG_001E|GPUREG_001E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 001F&lt;br /&gt;
| [[#GPUREG_001F|GPUREG_001F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0020&lt;br /&gt;
| [[#GPUREG_0020|GPUREG_0020]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0021&lt;br /&gt;
| [[#GPUREG_0021|GPUREG_0021]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0022&lt;br /&gt;
| [[#GPUREG_0022|GPUREG_0022]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0023&lt;br /&gt;
| [[#GPUREG_0023|GPUREG_0023]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0024&lt;br /&gt;
| [[#GPUREG_0024|GPUREG_0024]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0025&lt;br /&gt;
| [[#GPUREG_0025|GPUREG_0025]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0026&lt;br /&gt;
| [[#GPUREG_0026|GPUREG_0026]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0027&lt;br /&gt;
| [[#GPUREG_0027|GPUREG_0027]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0028&lt;br /&gt;
| [[#GPUREG_0028|GPUREG_0028]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0029&lt;br /&gt;
| [[#GPUREG_0029|GPUREG_0029]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 002A&lt;br /&gt;
| [[#GPUREG_002A|GPUREG_002A]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 002B&lt;br /&gt;
| [[#GPUREG_002B|GPUREG_002B]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 002C&lt;br /&gt;
| [[#GPUREG_002C|GPUREG_002C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 002D&lt;br /&gt;
| [[#GPUREG_002D|GPUREG_002D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 002E&lt;br /&gt;
| [[#GPUREG_002E|GPUREG_002E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 002F&lt;br /&gt;
| [[#GPUREG_002F|GPUREG_002F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0030&lt;br /&gt;
| [[#GPUREG_0030|GPUREG_0030]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0031&lt;br /&gt;
| [[#GPUREG_0031|GPUREG_0031]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0032&lt;br /&gt;
| [[#GPUREG_0032|GPUREG_0032]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0033&lt;br /&gt;
| [[#GPUREG_0033|GPUREG_0033]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0034&lt;br /&gt;
| [[#GPUREG_0034|GPUREG_0034]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0035&lt;br /&gt;
| [[#GPUREG_0035|GPUREG_0035]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0036&lt;br /&gt;
| [[#GPUREG_0036|GPUREG_0036]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0037&lt;br /&gt;
| [[#GPUREG_0037|GPUREG_0037]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0038&lt;br /&gt;
| [[#GPUREG_0038|GPUREG_0038]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0039&lt;br /&gt;
| [[#GPUREG_0039|GPUREG_0039]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 003A&lt;br /&gt;
| [[#GPUREG_003A|GPUREG_003A]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 003B&lt;br /&gt;
| [[#GPUREG_003B|GPUREG_003B]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 003C&lt;br /&gt;
| [[#GPUREG_003C|GPUREG_003C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 003D&lt;br /&gt;
| [[#GPUREG_003D|GPUREG_003D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 003E&lt;br /&gt;
| [[#GPUREG_003E|GPUREG_003E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 003F&lt;br /&gt;
| [[#GPUREG_003F|GPUREG_003F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Rasterizer registers (0x040-0x07F) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Register ID&lt;br /&gt;
! Register name&lt;br /&gt;
! Notes&lt;br /&gt;
! Official Name&lt;br /&gt;
|-&lt;br /&gt;
| 0040&lt;br /&gt;
| [[#GPUREG_FACECULLING_CONFIG|GPUREG_FACECULLING_CONFIG]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_CULL_FACE&lt;br /&gt;
|-&lt;br /&gt;
| 0041&lt;br /&gt;
| [[#GPUREG_VIEWPORT_WIDTH|GPUREG_VIEWPORT_WIDTH]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_VIEWPORT_WIDTH1&lt;br /&gt;
|-&lt;br /&gt;
| 0042&lt;br /&gt;
| [[#GPUREG_VIEWPORT_INVW|GPUREG_VIEWPORT_INVW]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_VIEWPORT_WIDTH2&lt;br /&gt;
|-&lt;br /&gt;
| 0043&lt;br /&gt;
| [[#GPUREG_VIEWPORT_HEIGHT|GPUREG_VIEWPORT_HEIGHT]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_VIEWPORT_HEIGHT1&lt;br /&gt;
|-&lt;br /&gt;
| 0044&lt;br /&gt;
| [[#GPUREG_VIEWPORT_INVH|GPUREG_VIEWPORT_INVH]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_VIEWPORT_HEIGHT2&lt;br /&gt;
|-&lt;br /&gt;
| 0045&lt;br /&gt;
| [[#GPUREG_0045|GPUREG_0045]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0046&lt;br /&gt;
| [[#GPUREG_0046|GPUREG_0046]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0047&lt;br /&gt;
| [[#GPUREG_FRAGOP_CLIP|GPUREG_FRAGOP_CLIP]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAGOP_CLIP&lt;br /&gt;
|-&lt;br /&gt;
| 0048&lt;br /&gt;
| [[#GPUREG_FRAGOP_CLIP_DATAi|GPUREG_FRAGOP_CLIP_DATA0]]&lt;br /&gt;
|? &lt;br /&gt;
|PICA_REG_FRAGOP_CLIP_DATA1&lt;br /&gt;
|-&lt;br /&gt;
| 0049&lt;br /&gt;
| [[#GPUREG_FRAGOP_CLIP_DATAi|GPUREG_FRAGOP_CLIP_DATA1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAGOP_CLIP_DATA2&lt;br /&gt;
|-&lt;br /&gt;
| 004A&lt;br /&gt;
| [[#GPUREG_FRAGOP_CLIP_DATAi|GPUREG_FRAGOP_CLIP_DATA2]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAGOP_CLIP_DATA3&lt;br /&gt;
|-&lt;br /&gt;
| 004B&lt;br /&gt;
| [[#GPUREG_FRAGOP_CLIP_DATAi|GPUREG_FRAGOP_CLIP_DATA3]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAGOP_CLIP_DATA4&lt;br /&gt;
|-&lt;br /&gt;
| 004C&lt;br /&gt;
| [[#GPUREG_004C|GPUREG_004C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 004D&lt;br /&gt;
| [[#GPUREG_DEPTHMAP_SCALE|GPUREG_DEPTHMAP_SCALE]]&lt;br /&gt;
| As f24&lt;br /&gt;
|PICA_REG_FRAGOP_WSCALE_DATA1&lt;br /&gt;
|-&lt;br /&gt;
| 004E&lt;br /&gt;
| [[#GPUREG_DEPTHMAP_OFFSET|GPUREG_DEPTHMAP_OFFSET]]&lt;br /&gt;
| As f24&lt;br /&gt;
|PICA_REG_FRAGOP_WSCALE_DATA2&lt;br /&gt;
|-&lt;br /&gt;
| 004F&lt;br /&gt;
| [[#GPUREG_SH_OUTMAP_TOTAL|GPUREG_SH_OUTMAP_TOTAL]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_OUT_REG_NUM0 / PICA_REG_VS_OUT_REG_NUM0&lt;br /&gt;
|-&lt;br /&gt;
| 0050&lt;br /&gt;
| [[#GPUREG_SH_OUTMAP_Oi|GPUREG_SH_OUTMAP_O0]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_OUT_ATTR0 / PICA_REG_VS_OUT_ATTR0&lt;br /&gt;
|-&lt;br /&gt;
| 0051&lt;br /&gt;
| [[#GPUREG_SH_OUTMAP_Oi|GPUREG_SH_OUTMAP_O1]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_OUT_ATTR1 / PICA_REG_VS_OUT_ATTR1&lt;br /&gt;
|-&lt;br /&gt;
| 0052&lt;br /&gt;
| [[#GPUREG_SH_OUTMAP_Oi|GPUREG_SH_OUTMAP_O2]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_OUT_ATTR2 / PICA_REG_VS_OUT_ATTR2&lt;br /&gt;
|-&lt;br /&gt;
| 0053&lt;br /&gt;
| [[#GPUREG_SH_OUTMAP_Oi|GPUREG_SH_OUTMAP_O3]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_OUT_ATTR3 / PICA_REG_VS_OUT_ATTR3&lt;br /&gt;
|-&lt;br /&gt;
| 0054&lt;br /&gt;
| [[#GPUREG_SH_OUTMAP_Oi|GPUREG_SH_OUTMAP_O4]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_OUT_ATTR4 / PICA_REG_VS_OUT_ATTR4&lt;br /&gt;
|-&lt;br /&gt;
| 0055&lt;br /&gt;
| [[#GPUREG_SH_OUTMAP_Oi|GPUREG_SH_OUTMAP_O5]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_OUT_ATTR5 / PICA_REG_VS_OUT_ATTR5&lt;br /&gt;
|-&lt;br /&gt;
| 0056&lt;br /&gt;
| [[#GPUREG_SH_OUTMAP_Oi|GPUREG_SH_OUTMAP_O6]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_OUT_ATTR6 / PICA_REG_VS_OUT_ATTR6&lt;br /&gt;
|-&lt;br /&gt;
| 0057&lt;br /&gt;
| [[#GPUREG_0057|GPUREG_0057]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0058&lt;br /&gt;
| [[#GPUREG_0058|GPUREG_0058]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0059&lt;br /&gt;
| [[#GPUREG_0059|GPUREG_0059]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 005A&lt;br /&gt;
| [[#GPUREG_005A|GPUREG_005A]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 005B&lt;br /&gt;
| [[#GPUREG_005B|GPUREG_005B]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 005C&lt;br /&gt;
| [[#GPUREG_005C|GPUREG_005C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 005D&lt;br /&gt;
| [[#GPUREG_005D|GPUREG_005D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 005E&lt;br /&gt;
| [[#GPUREG_005E|GPUREG_005E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 005F&lt;br /&gt;
| [[#GPUREG_005F|GPUREG_005F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0060&lt;br /&gt;
| [[#GPUREG_0060|GPUREG_0060]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0061&lt;br /&gt;
| [[#GPUREG_EARLYDEPTH_FUNC|GPUREG_EARLYDEPTH_FUNC]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_EARLY_DEPTH_FUNC&lt;br /&gt;
|-&lt;br /&gt;
| 0062&lt;br /&gt;
| [[#GPUREG_EARLYDEPTH_TEST1|GPUREG_EARLYDEPTH_TEST1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_EARLY_DEPTH_TEST1&lt;br /&gt;
|-&lt;br /&gt;
| 0063&lt;br /&gt;
| [[#GPUREG_EARLYDEPTH_CLEAR|GPUREG_EARLYDEPTH_CLEAR]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_EARLY_DEPTH_CLEAR&lt;br /&gt;
|-&lt;br /&gt;
| 0064&lt;br /&gt;
| [[#GPUREG_SH_OUTATTR_MODE|GPUREG_SH_OUTATTR_MODE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_GS_OUT_ATTR_MODE / PICA_REG_VS_OUT_ATTR_MODE&lt;br /&gt;
|-&lt;br /&gt;
| 0065&lt;br /&gt;
| [[#GPUREG_SCISSORTEST_MODE|GPUREG_SCISSORTEST_MODE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_SCISSOR&lt;br /&gt;
|-&lt;br /&gt;
| 0066&lt;br /&gt;
| [[#GPUREG_SCISSORTEST_POS|GPUREG_SCISSORTEST_POS]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_SCISSOR_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0067&lt;br /&gt;
| [[#GPUREG_SCISSORTEST_DIM|GPUREG_SCISSORTEST_DIM]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_SCISSOR_SIZE&lt;br /&gt;
|-&lt;br /&gt;
| 0068&lt;br /&gt;
| [[#GPUREG_VIEWPORT_XY|GPUREG_VIEWPORT_XY]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_VIEWPORT_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0069&lt;br /&gt;
| [[#GPUREG_0069|GPUREG_0069]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 006A&lt;br /&gt;
| [[#GPUREG_EARLYDEPTH_DATA|GPUREG_EARLYDEPTH_DATA]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_EARLY_DEPTH_DATA&lt;br /&gt;
|-&lt;br /&gt;
| 006B&lt;br /&gt;
| [[#GPUREG_006B|GPUREG_006B]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 006C&lt;br /&gt;
| [[#GPUREG_006C|GPUREG_006C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 006D&lt;br /&gt;
| [[#GPUREG_DEPTHMAP_ENABLE|GPUREG_DEPTHMAP_ENABLE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAGOP_WSCALE&lt;br /&gt;
|-&lt;br /&gt;
| 006E&lt;br /&gt;
| [[#GPUREG_RENDERBUF_DIM|GPUREG_RENDERBUF_DIM]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_RENDER_BUF_RESOLUTION1&lt;br /&gt;
|-&lt;br /&gt;
| 006F&lt;br /&gt;
| [[#GPUREG_SH_OUTATTR_CLOCK|GPUREG_SH_OUTATTR_CLOCK]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_GS_OUT_ATTR_CLK / PICA_REG_VS_OUT_ATTR_CLK&lt;br /&gt;
|-&lt;br /&gt;
| 0070&lt;br /&gt;
| [[#GPUREG_0070|GPUREG_0070]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0071&lt;br /&gt;
| [[#GPUREG_0071|GPUREG_0071]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0072&lt;br /&gt;
| [[#GPUREG_0072|GPUREG_0072]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0073&lt;br /&gt;
| [[#GPUREG_0073|GPUREG_0073]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0074&lt;br /&gt;
| [[#GPUREG_0074|GPUREG_0074]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0075&lt;br /&gt;
| [[#GPUREG_0075|GPUREG_0075]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0076&lt;br /&gt;
| [[#GPUREG_0076|GPUREG_0076]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0077&lt;br /&gt;
| [[#GPUREG_0077|GPUREG_0077]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0078&lt;br /&gt;
| [[#GPUREG_0078|GPUREG_0078]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0079&lt;br /&gt;
| [[#GPUREG_0079|GPUREG_0079]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 007A&lt;br /&gt;
| [[#GPUREG_007A|GPUREG_007A]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 007B&lt;br /&gt;
| [[#GPUREG_007B|GPUREG_007B]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 007C&lt;br /&gt;
| [[#GPUREG_007C|GPUREG_007C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 007D&lt;br /&gt;
| [[#GPUREG_007D|GPUREG_007D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 007E&lt;br /&gt;
| [[#GPUREG_007E|GPUREG_007E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 007F&lt;br /&gt;
| [[#GPUREG_007F|GPUREG_007F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Texturing registers (0x080-0x0FF) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Register ID&lt;br /&gt;
! Register name&lt;br /&gt;
! Notes&lt;br /&gt;
! Official Name&lt;br /&gt;
|-&lt;br /&gt;
| 0080&lt;br /&gt;
| [[#GPUREG_TEXUNIT_CONFIG|GPUREG_TEXUNIT_CONFIG]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_TEXTURE_FUNC&lt;br /&gt;
|-&lt;br /&gt;
| 0081&lt;br /&gt;
| [[#GPUREG_TEXUNITi_BORDER_COLOR|GPUREG_TEXUNIT0_BORDER_COLOR]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_TEXTURE0_BORDER_COLOR&lt;br /&gt;
|-&lt;br /&gt;
| 0082&lt;br /&gt;
| [[#GPUREG_TEXUNITi_DIM|GPUREG_TEXUNIT0_DIM]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_TEXTURE0_SIZE&lt;br /&gt;
|-&lt;br /&gt;
| 0083&lt;br /&gt;
| [[#GPUREG_TEXUNITi_PARAM|GPUREG_TEXUNIT0_PARAM]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEXTURE0_WRAP_FILTER&lt;br /&gt;
|-&lt;br /&gt;
| 0084&lt;br /&gt;
| [[#GPUREG_TEXUNITi_LOD|GPUREG_TEXUNIT0_LOD]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_TEXTURE0_LOD&lt;br /&gt;
|-&lt;br /&gt;
| 0085&lt;br /&gt;
| [[#GPUREG_TEXUNITi_ADDRi|GPUREG_TEXUNIT0_ADDR1]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEXTURE0_ADDR1&lt;br /&gt;
|-&lt;br /&gt;
| 0086&lt;br /&gt;
| [[#GPUREG_TEXUNITi_ADDRi|GPUREG_TEXUNIT0_ADDR2]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEXTURE0_ADDR2&lt;br /&gt;
|-&lt;br /&gt;
| 0087&lt;br /&gt;
| [[#GPUREG_TEXUNITi_ADDRi|GPUREG_TEXUNIT0_ADDR3]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEXTURE0_ADDR3&lt;br /&gt;
|-&lt;br /&gt;
| 0088&lt;br /&gt;
| [[#GPUREG_TEXUNITi_ADDRi|GPUREG_TEXUNIT0_ADDR4]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEXTURE0_ADDR4&lt;br /&gt;
|-&lt;br /&gt;
| 0089&lt;br /&gt;
| [[#GPUREG_TEXUNITi_ADDRi|GPUREG_TEXUNIT0_ADDR5]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEXTURE0_ADDR5&lt;br /&gt;
|-&lt;br /&gt;
| 008A&lt;br /&gt;
| [[#GPUREG_TEXUNITi_ADDRi|GPUREG_TEXUNIT0_ADDR6]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEXTURE0_ADDR6&lt;br /&gt;
|-&lt;br /&gt;
| 008B&lt;br /&gt;
| [[#GPUREG_TEXUNITi_SHADOW|GPUREG_TEXUNIT0_SHADOW]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_TEXTURE0_SHADOW&lt;br /&gt;
|-&lt;br /&gt;
| 008C&lt;br /&gt;
| [[#GPUREG_008C|GPUREG_008C]]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 008D&lt;br /&gt;
| [[#GPUREG_008D|GPUREG_008D]]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 008E&lt;br /&gt;
| [[#GPUREG_TEXUNITi_TYPE|GPUREG_TEXUNIT0_TYPE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_TEXTURE0_FORMAT&lt;br /&gt;
|-&lt;br /&gt;
| 008F&lt;br /&gt;
| [[#GPUREG_LIGHTING_ENABLE0|GPUREG_LIGHTING_ENABLE0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_EN0&lt;br /&gt;
|-&lt;br /&gt;
| 0090&lt;br /&gt;
| [[#GPUREG_0090|GPUREG_0090]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0091&lt;br /&gt;
| [[#GPUREG_TEXUNITi_BORDER_COLOR|GPUREG_TEXUNIT1_BORDER_COLOR]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_TEXTURE1_BORDER_COLOR&lt;br /&gt;
|-&lt;br /&gt;
| 0092&lt;br /&gt;
| [[#GPUREG_TEXUNITi_DIM|GPUREG_TEXUNIT1_DIM]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEXTURE1_SIZE&lt;br /&gt;
|-&lt;br /&gt;
| 0093&lt;br /&gt;
| [[#GPUREG_TEXUNITi_PARAM|GPUREG_TEXUNIT1_PARAM]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEXTURE1_WRAP_FILTER&lt;br /&gt;
|-&lt;br /&gt;
| 0094&lt;br /&gt;
| [[#GPUREG_TEXUNITi_LOD|GPUREG_TEXUNIT1_LOD]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_TEXTURE1_LOD&lt;br /&gt;
|-&lt;br /&gt;
| 0095&lt;br /&gt;
| [[#GPUREG_TEXUNITi_ADDRi|GPUREG_TEXUNIT1_ADDR]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEXTURE1_ADDR&lt;br /&gt;
|-&lt;br /&gt;
| 0096&lt;br /&gt;
| [[#GPUREG_TEXUNITi_TYPE|GPUREG_TEXUNIT1_TYPE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEXTURE1_FORMAT&lt;br /&gt;
|-&lt;br /&gt;
| 0097&lt;br /&gt;
| [[#GPUREG_0097|GPUREG_0097]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0098&lt;br /&gt;
| [[#GPUREG_0098|GPUREG_0098]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0099&lt;br /&gt;
| [[#GPUREG_TEXUNITi_BORDER_COLOR|GPUREG_TEXUNIT2_BORDER_COLOR]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_TEXTURE2_BORDER_COLOR&lt;br /&gt;
|-&lt;br /&gt;
| 009A&lt;br /&gt;
| [[#GPUREG_TEXUNITi_DIM|GPUREG_TEXUNIT2_DIM]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEXTURE2_SIZE&lt;br /&gt;
|-&lt;br /&gt;
| 009B&lt;br /&gt;
| [[#GPUREG_TEXUNITi_PARAM|GPUREG_TEXUNIT2_PARAM]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEXTURE2_WRAP_FILTER&lt;br /&gt;
|-&lt;br /&gt;
| 009C&lt;br /&gt;
| [[#GPUREG_TEXUNITi_LOD|GPUREG_TEXUNIT2_LOD]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_TEXTURE2_LOD&lt;br /&gt;
|-&lt;br /&gt;
| 009D&lt;br /&gt;
| [[#GPUREG_TEXUNITi_ADDRi|GPUREG_TEXUNIT2_ADDR]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_TEXTURE2_ADDR&lt;br /&gt;
|-&lt;br /&gt;
| 009E&lt;br /&gt;
| [[#GPUREG_TEXUNITi_TYPE|GPUREG_TEXUNIT2_TYPE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEXTURE2_FORMAT&lt;br /&gt;
|-&lt;br /&gt;
| 009F&lt;br /&gt;
| [[#GPUREG_009F|GPUREG_009F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00A0&lt;br /&gt;
| [[#GPUREG_00A0|GPUREG_00A0]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00A1&lt;br /&gt;
| [[#GPUREG_00A1|GPUREG_00A1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00A2&lt;br /&gt;
| [[#GPUREG_00A2|GPUREG_00A2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00A3&lt;br /&gt;
| [[#GPUREG_00A3|GPUREG_00A3]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00A4&lt;br /&gt;
| [[#GPUREG_00A4|GPUREG_00A4]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00A5&lt;br /&gt;
| [[#GPUREG_00A5|GPUREG_00A5]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00A6&lt;br /&gt;
| [[#GPUREG_00A6|GPUREG_00A6]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00A7&lt;br /&gt;
| [[#GPUREG_00A7|GPUREG_00A7]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00A8&lt;br /&gt;
| [[#GPUREG_TEXUNIT3_PROCTEX0|GPUREG_TEXUNIT3_PROCTEX0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_TEXTURE3_PROCTEX0&lt;br /&gt;
|-&lt;br /&gt;
| 00A9&lt;br /&gt;
| [[#GPUREG_TEXUNIT3_PROCTEX1|GPUREG_TEXUNIT3_PROCTEX1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_TEXTURE3_PROCTEX1&lt;br /&gt;
|-&lt;br /&gt;
| 00AA&lt;br /&gt;
| [[#GPUREG_TEXUNIT3_PROCTEX2|GPUREG_TEXUNIT3_PROCTEX2]]&lt;br /&gt;
|? &lt;br /&gt;
|PICA_REG_TEXTURE3_PROCTEX2&lt;br /&gt;
|-&lt;br /&gt;
| 00AB&lt;br /&gt;
| [[#GPUREG_TEXUNIT3_PROCTEX3|GPUREG_TEXUNIT3_PROCTEX3]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_TEXTURE3_PROCTEX3&lt;br /&gt;
|-&lt;br /&gt;
| 00AC&lt;br /&gt;
| [[#GPUREG_TEXUNIT3_PROCTEX4|GPUREG_TEXUNIT3_PROCTEX4]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_TEXTURE3_PROCTEX4&lt;br /&gt;
|-&lt;br /&gt;
| 00AD&lt;br /&gt;
| [[#GPUREG_TEXUNIT3_PROCTEX5|GPUREG_TEXUNIT3_PROCTEX5]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_TEXTURE3_PROCTEX5&lt;br /&gt;
|-&lt;br /&gt;
| 00AE&lt;br /&gt;
| [[#GPUREG_00AE|GPUREG_00AE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00AF&lt;br /&gt;
| [[#GPUREG_PROCTEX_LUT|GPUREG_PROCTEX_LUT]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_PROCTEX_LUT&lt;br /&gt;
|-&lt;br /&gt;
| 00B0&lt;br /&gt;
| [[#GPUREG_PROCTEX_LUT_DATAi|GPUREG_PROCTEX_LUT_DATA0]]&lt;br /&gt;
| ?&lt;br /&gt;
|PICA_REG_PROCTEX_LUT_DATA0&lt;br /&gt;
|-&lt;br /&gt;
| 00B1&lt;br /&gt;
| [[#GPUREG_PROCTEX_LUT_DATAi|GPUREG_PROCTEX_LUT_DATA1]]&lt;br /&gt;
| ?&lt;br /&gt;
|PICA_REG_PROCTEX_LUT_DATA1&lt;br /&gt;
|-&lt;br /&gt;
| 00B2&lt;br /&gt;
| [[#GPUREG_PROCTEX_LUT_DATAi|GPUREG_PROCTEX_LUT_DATA2]]&lt;br /&gt;
| ?&lt;br /&gt;
|PICA_REG_PROCTEX_LUT_DATA2&lt;br /&gt;
|-&lt;br /&gt;
| 00B3&lt;br /&gt;
| [[#GPUREG_PROCTEX_LUT_DATAi|GPUREG_PROCTEX_LUT_DATA3]]&lt;br /&gt;
| ?&lt;br /&gt;
|PICA_REG_PROCTEX_LUT_DATA3&lt;br /&gt;
|-&lt;br /&gt;
| 00B4&lt;br /&gt;
| [[#GPUREG_PROCTEX_LUT_DATAi|GPUREG_PROCTEX_LUT_DATA4]]&lt;br /&gt;
| ?&lt;br /&gt;
|PICA_REG_PROCTEX_LUT_DATA4&lt;br /&gt;
|-&lt;br /&gt;
| 00B5&lt;br /&gt;
| [[#GPUREG_PROCTEX_LUT_DATAi|GPUREG_PROCTEX_LUT_DATA5]]&lt;br /&gt;
| ?&lt;br /&gt;
|PICA_REG_PROCTEX_LUT_DATA5&lt;br /&gt;
|-&lt;br /&gt;
| 00B6&lt;br /&gt;
| [[#GPUREG_PROCTEX_LUT_DATAi|GPUREG_PROCTEX_LUT_DATA6]]&lt;br /&gt;
| ?&lt;br /&gt;
|PICA_REG_PROCTEX_LUT_DATA6&lt;br /&gt;
|-&lt;br /&gt;
| 00B7&lt;br /&gt;
| [[#GPUREG_PROCTEX_LUT_DATAi|GPUREG_PROCTEX_LUT_DATA7]]&lt;br /&gt;
| ?&lt;br /&gt;
|PICA_REG_PROCTEX_LUT_DATA7&lt;br /&gt;
|-&lt;br /&gt;
| 00B8&lt;br /&gt;
| [[#GPUREG_00B8|GPUREG_00B8]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00B9&lt;br /&gt;
| [[#GPUREG_00B9|GPUREG_00B9]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00BA&lt;br /&gt;
| [[#GPUREG_00BA|GPUREG_00BA]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00BB&lt;br /&gt;
| [[#GPUREG_00BB|GPUREG_00BB]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00BC&lt;br /&gt;
| [[#GPUREG_00BC|GPUREG_00BC]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00BD&lt;br /&gt;
| [[#GPUREG_00BD|GPUREG_00BD]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00BE&lt;br /&gt;
| [[#GPUREG_00BE|GPUREG_00BE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00BF&lt;br /&gt;
| [[#GPUREG_00BF|GPUREG_00BF]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00C0&lt;br /&gt;
| [[#GPUREG_TEXENVi_SOURCE|GPUREG_TEXENV0_SOURCE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_0&lt;br /&gt;
|-&lt;br /&gt;
| 00C1&lt;br /&gt;
| [[#GPUREG_TEXENVi_OPERAND|GPUREG_TEXENV0_OPERAND]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_0_OPERAND&lt;br /&gt;
|-&lt;br /&gt;
| 00C2&lt;br /&gt;
| [[#GPUREG_TEXENVi_COMBINER|GPUREG_TEXENV0_COMBINER]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_0_COMBINE&lt;br /&gt;
|-&lt;br /&gt;
| 00C3&lt;br /&gt;
| [[#GPUREG_TEXENVi_COLOR|GPUREG_TEXENV0_COLOR]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_0_COLOR&lt;br /&gt;
|-&lt;br /&gt;
| 00C4&lt;br /&gt;
| [[#GPUREG_TEXENVi_SCALE|GPUREG_TEXENV0_SCALE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_0_SCALE&lt;br /&gt;
|-&lt;br /&gt;
| 00C5&lt;br /&gt;
| [[#GPUREG_00C5|GPUREG_00C5]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00C6&lt;br /&gt;
| [[#GPUREG_00C6|GPUREG_00C6]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00C7&lt;br /&gt;
| [[#GPUREG_00C7|GPUREG_00C7]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00C8&lt;br /&gt;
| [[#GPUREG_TEXENVi_SOURCE|GPUREG_TEXENV1_SOURCE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_1&lt;br /&gt;
|-&lt;br /&gt;
| 00C9&lt;br /&gt;
| [[#GPUREG_TEXENVi_OPERAND|GPUREG_TEXENV1_OPERAND]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_1_OPERAND&lt;br /&gt;
|-&lt;br /&gt;
| 00CA&lt;br /&gt;
| [[#GPUREG_TEXENVi_COMBINER|GPUREG_TEXENV1_COMBINER]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_1_COMBINE&lt;br /&gt;
|-&lt;br /&gt;
| 00CB&lt;br /&gt;
| [[#GPUREG_TEXENVi_COLOR|GPUREG_TEXENV1_COLOR]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_1_COLOR&lt;br /&gt;
|-&lt;br /&gt;
| 00CC&lt;br /&gt;
| [[#GPUREG_TEXENVi_SCALE|GPUREG_TEXENV1_SCALE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_1_SCALE&lt;br /&gt;
|-&lt;br /&gt;
| 00CD&lt;br /&gt;
| [[#GPUREG_00CD|GPUREG_00CD]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00CE&lt;br /&gt;
| [[#GPUREG_00CE|GPUREG_00CE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00CF&lt;br /&gt;
| [[#GPUREG_00CF|GPUREG_00CF]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00D0&lt;br /&gt;
| [[#GPUREG_TEXENVi_SOURCE|GPUREG_TEXENV2_SOURCE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_2&lt;br /&gt;
|-&lt;br /&gt;
| 00D1&lt;br /&gt;
| [[#GPUREG_TEXENVi_OPERAND|GPUREG_TEXENV2_OPERAND]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_2_OPERAND&lt;br /&gt;
|-&lt;br /&gt;
| 00D2&lt;br /&gt;
| [[#GPUREG_TEXENVi_COMBINER|GPUREG_TEXENV2_COMBINER]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_2_COMBINE&lt;br /&gt;
|-&lt;br /&gt;
| 00D3&lt;br /&gt;
| [[#GPUREG_TEXENVi_COLOR|GPUREG_TEXENV2_COLOR]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_2_COLOR&lt;br /&gt;
|-&lt;br /&gt;
| 00D4&lt;br /&gt;
| [[#GPUREG_TEXENVi_SCALE|GPUREG_TEXENV2_SCALE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_2_SCALE&lt;br /&gt;
|-&lt;br /&gt;
| 00D5&lt;br /&gt;
| [[#GPUREG_00D5|GPUREG_00D5]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00D6&lt;br /&gt;
| [[#GPUREG_00D6|GPUREG_00D6]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00D7&lt;br /&gt;
| [[#GPUREG_00D7|GPUREG_00D7]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00D8&lt;br /&gt;
| [[#GPUREG_TEXENVi_SOURCE|GPUREG_TEXENV3_SOURCE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_3&lt;br /&gt;
|-&lt;br /&gt;
| 00D9&lt;br /&gt;
| [[#GPUREG_TEXENVi_OPERAND|GPUREG_TEXENV3_OPERAND]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_3_OPERAND&lt;br /&gt;
|-&lt;br /&gt;
| 00DA&lt;br /&gt;
| [[#GPUREG_TEXENVi_COMBINER|GPUREG_TEXENV3_COMBINER]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_3_COMBINE&lt;br /&gt;
|-&lt;br /&gt;
| 00DB&lt;br /&gt;
| [[#GPUREG_TEXENVi_COLOR|GPUREG_TEXENV3_COLOR]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_3_COLOR&lt;br /&gt;
|-&lt;br /&gt;
| 00DC&lt;br /&gt;
| [[#GPUREG_TEXENVi_SCALE|GPUREG_TEXENV3_SCALE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_3_SCALE&lt;br /&gt;
|-&lt;br /&gt;
| 00DD&lt;br /&gt;
| [[#GPUREG_00DD|GPUREG_00DD]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00DE&lt;br /&gt;
| [[#GPUREG_00DE|GPUREG_00DE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00DF&lt;br /&gt;
| [[#GPUREG_00DF|GPUREG_00DF]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00E0&lt;br /&gt;
| [[#GPUREG_TEXENV_UPDATE_BUFFER|GPUREG_TEXENV_UPDATE_BUFFER]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_GAS_FOG_MODE / PICA_REG_TEX_ENV_BUF_INPUT&lt;br /&gt;
|-&lt;br /&gt;
| 00E1&lt;br /&gt;
| [[#GPUREG_FOG_COLOR|GPUREG_FOG_COLOR]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FOG_COLOR&lt;br /&gt;
|-&lt;br /&gt;
| 00E2&lt;br /&gt;
| [[#GPUREG_00E2|GPUREG_00E2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00E3&lt;br /&gt;
| [[#GPUREG_00E3|GPUREG_00E3]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00E4&lt;br /&gt;
| [[#GPUREG_GAS_ATTENUATION|GPUREG_GAS_ATTENUATION]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_GAS_ATTENUATION&lt;br /&gt;
|-&lt;br /&gt;
| 00E5&lt;br /&gt;
| [[#GPUREG_GAS_ACCMAX|GPUREG_GAS_ACCMAX]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_GAS_ACCMAX&lt;br /&gt;
|-&lt;br /&gt;
| 00E6&lt;br /&gt;
| [[#GPUREG_FOG_LUT_INDEX|GPUREG_FOG_LUT_INDEX]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FOG_LUT_INDEX&lt;br /&gt;
|-&lt;br /&gt;
| 00E7&lt;br /&gt;
| [[#GPUREG_00E7|GPUREG_00E7]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00E8&lt;br /&gt;
| [[#GPUREG_FOG_LUT_DATAi|GPUREG_FOG_LUT_DATA0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FOG_LUT_DATA0&lt;br /&gt;
|-&lt;br /&gt;
| 00E9&lt;br /&gt;
| [[#GPUREG_FOG_LUT_DATAi|GPUREG_FOG_LUT_DATA1]]&lt;br /&gt;
|? &lt;br /&gt;
|PICA_REG_FOG_LUT_DATA1&lt;br /&gt;
|-&lt;br /&gt;
| 00EA&lt;br /&gt;
| [[#GPUREG_FOG_LUT_DATAi|GPUREG_FOG_LUT_DATA2]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FOG_LUT_DATA2&lt;br /&gt;
|-&lt;br /&gt;
| 00EB&lt;br /&gt;
| [[#GPUREG_FOG_LUT_DATAi|GPUREG_FOG_LUT_DATA3]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FOG_LUT_DATA3&lt;br /&gt;
|-&lt;br /&gt;
| 00EC&lt;br /&gt;
| [[#GPUREG_FOG_LUT_DATAi|GPUREG_FOG_LUT_DATA4]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FOG_LUT_DATA4&lt;br /&gt;
|-&lt;br /&gt;
| 00ED&lt;br /&gt;
| [[#GPUREG_FOG_LUT_DATAi|GPUREG_FOG_LUT_DATA5]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FOG_LUT_DATA5&lt;br /&gt;
|-&lt;br /&gt;
| 00EE&lt;br /&gt;
| [[#GPUREG_FOG_LUT_DATAi|GPUREG_FOG_LUT_DATA6]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FOG_LUT_DATA6&lt;br /&gt;
|-&lt;br /&gt;
| 00EF&lt;br /&gt;
| [[#GPUREG_FOG_LUT_DATAi|GPUREG_FOG_LUT_DATA7]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FOG_LUT_DATA7&lt;br /&gt;
|-&lt;br /&gt;
| 00F0&lt;br /&gt;
| [[#GPUREG_TEXENVi_SOURCE|GPUREG_TEXENV4_SOURCE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_4&lt;br /&gt;
|-&lt;br /&gt;
| 00F1&lt;br /&gt;
| [[#GPUREG_TEXENVi_OPERAND|GPUREG_TEXENV4_OPERAND]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_4_OPERAND&lt;br /&gt;
|-&lt;br /&gt;
| 00F2&lt;br /&gt;
| [[#GPUREG_TEXENVi_COMBINER|GPUREG_TEXENV4_COMBINER]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_4_COMBINE&lt;br /&gt;
|-&lt;br /&gt;
| 00F3&lt;br /&gt;
| [[#GPUREG_TEXENVi_COLOR|GPUREG_TEXENV4_COLOR]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_4_COLOR&lt;br /&gt;
|-&lt;br /&gt;
| 00F4&lt;br /&gt;
| [[#GPUREG_TEXENVi_SCALE|GPUREG_TEXENV4_SCALE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_4_SCALE&lt;br /&gt;
|-&lt;br /&gt;
| 00F5&lt;br /&gt;
| [[#GPUREG_00F5|GPUREG_00F5]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00F6&lt;br /&gt;
| [[#GPUREG_00F6|GPUREG_00F6]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00F7&lt;br /&gt;
| [[#GPUREG_00F7|GPUREG_00F7]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00F8&lt;br /&gt;
| [[#GPUREG_TEXENVi_SOURCE|GPUREG_TEXENV5_SOURCE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_5&lt;br /&gt;
|-&lt;br /&gt;
| 00F9&lt;br /&gt;
| [[#GPUREG_TEXENVi_OPERAND|GPUREG_TEXENV5_OPERAND]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_5_OPERAND&lt;br /&gt;
|-&lt;br /&gt;
| 00FA&lt;br /&gt;
| [[#GPUREG_TEXENVi_COMBINER|GPUREG_TEXENV5_COMBINER]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_5_COMBINE&lt;br /&gt;
|-&lt;br /&gt;
| 00FB&lt;br /&gt;
| [[#GPUREG_TEXENVi_COLOR|GPUREG_TEXENV5_COLOR]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_5_COLOR&lt;br /&gt;
|-&lt;br /&gt;
| 00FC&lt;br /&gt;
| [[#GPUREG_TEXENVi_SCALE|GPUREG_TEXENV5_SCALE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_TEX_ENV_5_SCALE&lt;br /&gt;
|-&lt;br /&gt;
| 00FD&lt;br /&gt;
| [[#GPUREG_TEXENV_BUFFER_COLOR|GPUREG_TEXENV_BUFFER_COLOR]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_TEX_ENV_BUF_COLOR&lt;br /&gt;
|-&lt;br /&gt;
| 00FE&lt;br /&gt;
| [[#GPUREG_00FE|GPUREG_00FE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 00FF&lt;br /&gt;
| [[#GPUREG_00FF|GPUREG_00FF]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer registers (0x100-0x13F) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Register ID&lt;br /&gt;
! Register name&lt;br /&gt;
! Notes&lt;br /&gt;
! Official Name&lt;br /&gt;
|-&lt;br /&gt;
| 0100&lt;br /&gt;
| [[#GPUREG_COLOR_OPERATION|GPUREG_COLOR_OPERATION]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_COLOR_OPERATION&lt;br /&gt;
|-&lt;br /&gt;
| 0101&lt;br /&gt;
| [[#GPUREG_BLEND_FUNC|GPUREG_BLEND_FUNC]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_BLEND_FUNC&lt;br /&gt;
|-&lt;br /&gt;
| 0102&lt;br /&gt;
| [[#GPUREG_LOGIC_OP|GPUREG_LOGIC_OP]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_LOGIC_OP&lt;br /&gt;
|-&lt;br /&gt;
| 0103&lt;br /&gt;
| [[#GPUREG_BLEND_COLOR|GPUREG_BLEND_COLOR]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_BLEND_COLOR&lt;br /&gt;
|-&lt;br /&gt;
| 0104&lt;br /&gt;
| [[#GPUREG_FRAGOP_ALPHA_TEST|GPUREG_FRAGOP_ALPHA_TEST]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_FRAGOP_ALPHA_TEST&lt;br /&gt;
|-&lt;br /&gt;
| 0105&lt;br /&gt;
| [[#GPUREG_STENCIL_TEST|GPUREG_STENCIL_TEST]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_STENCIL_TEST&lt;br /&gt;
|-&lt;br /&gt;
| 0106&lt;br /&gt;
| [[#GPUREG_STENCIL_OP|GPUREG_STENCIL_OP]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_STENCIL_OP&lt;br /&gt;
|-&lt;br /&gt;
| 0107&lt;br /&gt;
| [[#GPUREG_DEPTH_COLOR_MASK|GPUREG_DEPTH_COLOR_MASK]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_DEPTH_COLOR_MASK&lt;br /&gt;
|-&lt;br /&gt;
| 0108&lt;br /&gt;
| [[#GPUREG_0108|GPUREG_0108]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0109&lt;br /&gt;
| [[#GPUREG_0109|GPUREG_0109]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 010A&lt;br /&gt;
| [[#GPUREG_010A|GPUREG_010A]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 010B&lt;br /&gt;
| [[#GPUREG_010B|GPUREG_010B]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 010C&lt;br /&gt;
| [[#GPUREG_010C|GPUREG_010C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 010D&lt;br /&gt;
| [[#GPUREG_010D|GPUREG_010D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 010E&lt;br /&gt;
| [[#GPUREG_010E|GPUREG_010E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 010F&lt;br /&gt;
| [[#GPUREG_010F|GPUREG_010F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0110&lt;br /&gt;
| [[#GPUREG_FRAMEBUFFER_INVALIDATE|GPUREG_FRAMEBUFFER_INVALIDATE]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_COLOR_BUFFER_CLEAR0&lt;br /&gt;
|-&lt;br /&gt;
| 0111&lt;br /&gt;
| [[#GPUREG_FRAMEBUFFER_FLUSH|GPUREG_FRAMEBUFFER_FLUSH]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_COLOR_BUFFER_CLEAR1&lt;br /&gt;
|-&lt;br /&gt;
| 0112&lt;br /&gt;
| [[#GPUREG_COLORBUFFER_READ|GPUREG_COLORBUFFER_READ]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_COLOR_BUFFER_READ&lt;br /&gt;
|-&lt;br /&gt;
| 0113&lt;br /&gt;
| [[#GPUREG_COLORBUFFER_WRITE|GPUREG_COLORBUFFER_WRITE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_COLOR_BUFFER_WRITE&lt;br /&gt;
|-&lt;br /&gt;
| 0114&lt;br /&gt;
| [[#GPUREG_DEPTHBUFFER_READ|GPUREG_DEPTHBUFFER_READ]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_DEPTH_STENCIL_READ&lt;br /&gt;
|-&lt;br /&gt;
| 0115&lt;br /&gt;
| [[#GPUREG_DEPTHBUFFER_WRITE|GPUREG_DEPTHBUFFER_WRITE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_DEPTH_STENCIL_WRITE&lt;br /&gt;
|-&lt;br /&gt;
| 0116&lt;br /&gt;
| [[#GPUREG_DEPTHBUFFER_FORMAT|GPUREG_DEPTHBUFFER_FORMAT]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_RENDER_BUF_DEPTH_MODE&lt;br /&gt;
|-&lt;br /&gt;
| 0117&lt;br /&gt;
| [[#GPUREG_COLORBUFFER_FORMAT|GPUREG_COLORBUFFER_FORMAT]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_RENDER_BUF_COLOR_MODE&lt;br /&gt;
|-&lt;br /&gt;
| 0118&lt;br /&gt;
| [[#GPUREG_EARLYDEPTH_TEST2|GPUREG_EARLYDEPTH_TEST2]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_EARLY_DEPTH_TEST2&lt;br /&gt;
|-&lt;br /&gt;
| 0119&lt;br /&gt;
| [[#GPUREG_0119|GPUREG_0119]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 011A&lt;br /&gt;
| [[#GPUREG_011A|GPUREG_011A]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 011B&lt;br /&gt;
| [[#GPUREG_FRAMEBUFFER_BLOCK32|GPUREG_FRAMEBUFFER_BLOCK32]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_RENDER_BLOCK_FORMAT&lt;br /&gt;
|-&lt;br /&gt;
| 011C&lt;br /&gt;
| [[#GPUREG_DEPTHBUFFER_LOC|GPUREG_DEPTHBUFFER_LOC]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_RENDER_BUF_DEPTH_ADDR&lt;br /&gt;
|-&lt;br /&gt;
| 011D&lt;br /&gt;
| [[#GPUREG_COLORBUFFER_LOC|GPUREG_COLORBUFFER_LOC]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_RENDER_BUF_COLOR_ADDR&lt;br /&gt;
|-&lt;br /&gt;
| 011E&lt;br /&gt;
| [[#GPUREG_FRAMEBUFFER_DIM|GPUREG_FRAMEBUFFER_DIM]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_RENDER_BUF_RESOLUTION0&lt;br /&gt;
|-&lt;br /&gt;
| 011F&lt;br /&gt;
| [[#GPUREG_011F|GPUREG_011F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0120&lt;br /&gt;
| [[#GPUREG_GAS_LIGHT_XY|GPUREG_GAS_LIGHT_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_GAS_LIGHT_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0121&lt;br /&gt;
| [[#GPUREG_GAS_LIGHT_Z|GPUREG_GAS_LIGHT_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_GAS_LIGHT_Z&lt;br /&gt;
|-&lt;br /&gt;
| 0122&lt;br /&gt;
| [[#GPUREG_GAS_LIGHT_Z_COLOR|GPUREG_GAS_LIGHT_Z_COLOR]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_GAS_LIGHT_Z_COLOR&lt;br /&gt;
|-&lt;br /&gt;
| 0123&lt;br /&gt;
| [[#GPUREG_GAS_LUT_INDEX|GPUREG_GAS_LUT_INDEX]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_GAS_LUT_INDEX&lt;br /&gt;
|-&lt;br /&gt;
| 0124&lt;br /&gt;
| [[#GPUREG_GAS_LUT_DATA|GPUREG_GAS_LUT_DATA]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_GAS_LUT_DATA&lt;br /&gt;
|-&lt;br /&gt;
| 0125&lt;br /&gt;
| [[#GPUREG_0125|GPUREG_0125]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0126&lt;br /&gt;
| [[#GPUREG_GAS_DELTAZ_DEPTH|GPUREG_GAS_DELTAZ_DEPTH]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_GAS_DELTAZ_DEPTH&lt;br /&gt;
|-&lt;br /&gt;
| 0127&lt;br /&gt;
| [[#GPUREG_0127|GPUREG_0127]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0128&lt;br /&gt;
| [[#GPUREG_0128|GPUREG_0128]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0129&lt;br /&gt;
| [[#GPUREG_0129|GPUREG_0129]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 012A&lt;br /&gt;
| [[#GPUREG_012A|GPUREG_012A]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 012B&lt;br /&gt;
| [[#GPUREG_012B|GPUREG_012B]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 012C&lt;br /&gt;
| [[#GPUREG_012C|GPUREG_012C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 012D&lt;br /&gt;
| [[#GPUREG_012D|GPUREG_012D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 012E&lt;br /&gt;
| [[#GPUREG_012E|GPUREG_012E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 012F&lt;br /&gt;
| [[#GPUREG_012F|GPUREG_012F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0130&lt;br /&gt;
| [[#GPUREG_FRAGOP_SHADOW|GPUREG_FRAGOP_SHADOW]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAGOP_SHADOW&lt;br /&gt;
|-&lt;br /&gt;
| 0131&lt;br /&gt;
| [[#GPUREG_0131|GPUREG_0131]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0132&lt;br /&gt;
| [[#GPUREG_0132|GPUREG_0132]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0133&lt;br /&gt;
| [[#GPUREG_0133|GPUREG_0133]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0134&lt;br /&gt;
| [[#GPUREG_0134|GPUREG_0134]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0135&lt;br /&gt;
| [[#GPUREG_0135|GPUREG_0135]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0136&lt;br /&gt;
| [[#GPUREG_0136|GPUREG_0136]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0137&lt;br /&gt;
| [[#GPUREG_0137|GPUREG_0137]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0138&lt;br /&gt;
| [[#GPUREG_0138|GPUREG_0138]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0139&lt;br /&gt;
| [[#GPUREG_0139|GPUREG_0139]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 013A&lt;br /&gt;
| [[#GPUREG_013A|GPUREG_013A]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 013B&lt;br /&gt;
| [[#GPUREG_013B|GPUREG_013B]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 013C&lt;br /&gt;
| [[#GPUREG_013C|GPUREG_013C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 013D&lt;br /&gt;
| [[#GPUREG_013D|GPUREG_013D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 013E&lt;br /&gt;
| [[#GPUREG_013E|GPUREG_013E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 013F&lt;br /&gt;
| [[#GPUREG_013F|GPUREG_013F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Fragment lighting registers (0x140-0x1FF) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Register ID&lt;br /&gt;
! Register name&lt;br /&gt;
! Notes&lt;br /&gt;
! Official Name&lt;br /&gt;
|-&lt;br /&gt;
| 0140&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR0|GPUREG_LIGHT0_SPECULAR0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT0_SPECULAR0 / PICA_REG_FRAG_LIGHT_START&lt;br /&gt;
|-&lt;br /&gt;
| 0141&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR1|GPUREG_LIGHT0_SPECULAR1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT0_SPECULAR1&lt;br /&gt;
|-&lt;br /&gt;
| 0142&lt;br /&gt;
| [[#GPUREG_LIGHTi_DIFFUSE|GPUREG_LIGHT0_DIFFUSE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT0_DIFFUSE&lt;br /&gt;
|-&lt;br /&gt;
| 0143&lt;br /&gt;
| [[#GPUREG_LIGHTi_AMBIENT|GPUREG_LIGHT0_AMBIENT]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT0_AMBIENT&lt;br /&gt;
|-&lt;br /&gt;
| 0144&lt;br /&gt;
| [[#GPUREG_LIGHTi_XY|GPUREG_LIGHT0_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT0_POSITION_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0145&lt;br /&gt;
| [[#GPUREG_LIGHTi_Z|GPUREG_LIGHT0_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT0_POSITION_Z&lt;br /&gt;
|-&lt;br /&gt;
| 0146&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_XY|GPUREG_LIGHT0_SPOTDIR_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT0_SPOT_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0147&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_Z|GPUREG_LIGHT0_SPOTDIR_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT0_SPOT_Z&lt;br /&gt;
|-&lt;br /&gt;
| 0148&lt;br /&gt;
| [[#GPUREG_0148|GPUREG_0148]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0149&lt;br /&gt;
| [[#GPUREG_LIGHTi_CONFIG|GPUREG_LIGHT0_CONFIG]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_FRAG_LIGHT0_TYPE&lt;br /&gt;
|-&lt;br /&gt;
| 014A&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_BIAS|GPUREG_LIGHT0_ATTENUATION_BIAS]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT0_DIST_ATTN_BIAS&lt;br /&gt;
|-&lt;br /&gt;
| 014B&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_SCALE|GPUREG_LIGHT0_ATTENUATION_SCALE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT0_DIST_ATTN_SCALE&lt;br /&gt;
|-&lt;br /&gt;
| 014C&lt;br /&gt;
| [[#GPUREG_014C|GPUREG_014C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 014D&lt;br /&gt;
| [[#GPUREG_014D|GPUREG_014D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 014E&lt;br /&gt;
| [[#GPUREG_014E|GPUREG_014E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 014F&lt;br /&gt;
| [[#GPUREG_014F|GPUREG_014F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0150&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR0|GPUREG_LIGHT1_SPECULAR0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT1_SPECULAR0&lt;br /&gt;
|-&lt;br /&gt;
| 0151&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR1|GPUREG_LIGHT1_SPECULAR1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT1_SPECULAR1&lt;br /&gt;
|-&lt;br /&gt;
| 0152&lt;br /&gt;
| [[#GPUREG_LIGHTi_DIFFUSE|GPUREG_LIGHT1_DIFFUSE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT1_DIFFUSE&lt;br /&gt;
|-&lt;br /&gt;
| 0153&lt;br /&gt;
| [[#GPUREG_LIGHTi_AMBIENT|GPUREG_LIGHT1_AMBIENT]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT1_AMBIENT&lt;br /&gt;
|-&lt;br /&gt;
| 0154&lt;br /&gt;
| [[#GPUREG_LIGHTi_XY|GPUREG_LIGHT1_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT1_POSITION_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0155&lt;br /&gt;
| [[#GPUREG_LIGHTi_Z|GPUREG_LIGHT1_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT1_POSITION_Z&lt;br /&gt;
|-&lt;br /&gt;
| 0156&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_XY|GPUREG_LIGHT1_SPOTDIR_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT1_SPOT_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0157&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_Z|GPUREG_LIGHT1_SPOTDIR_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT1_SPOT_Z&lt;br /&gt;
|-&lt;br /&gt;
| 0158&lt;br /&gt;
| [[#GPUREG_0158|GPUREG_0158]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0159&lt;br /&gt;
| [[#GPUREG_LIGHTi_CONFIG|GPUREG_LIGHT1_CONFIG]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT1_TYPE&lt;br /&gt;
|-&lt;br /&gt;
| 015A&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_BIAS|GPUREG_LIGHT1_ATTENUATION_BIAS]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT1_DIST_ATTN_BIAS&lt;br /&gt;
|-&lt;br /&gt;
| 015B&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_SCALE|GPUREG_LIGHT1_ATTENUATION_SCALE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT1_DIST_ATTN_SCALE&lt;br /&gt;
|-&lt;br /&gt;
| 015C&lt;br /&gt;
| [[#GPUREG_015C|GPUREG_015C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 015D&lt;br /&gt;
| [[#GPUREG_015D|GPUREG_015D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 015E&lt;br /&gt;
| [[#GPUREG_015E|GPUREG_015E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 015F&lt;br /&gt;
| [[#GPUREG_015F|GPUREG_015F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0160&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR0|GPUREG_LIGHT2_SPECULAR0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT2_SPECULAR0&lt;br /&gt;
|-&lt;br /&gt;
| 0161&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR1|GPUREG_LIGHT2_SPECULAR1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT2_SPECULAR1&lt;br /&gt;
|-&lt;br /&gt;
| 0162&lt;br /&gt;
| [[#GPUREG_LIGHTi_DIFFUSE|GPUREG_LIGHT2_DIFFUSE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT2_DIFFUSE&lt;br /&gt;
|-&lt;br /&gt;
| 0163&lt;br /&gt;
| [[#GPUREG_LIGHTi_AMBIENT|GPUREG_LIGHT2_AMBIENT]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT2_AMBIENT&lt;br /&gt;
|-&lt;br /&gt;
| 0164&lt;br /&gt;
| [[#GPUREG_LIGHTi_XY|GPUREG_LIGHT2_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT2_POSITION_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0165&lt;br /&gt;
| [[#GPUREG_LIGHTi_Z|GPUREG_LIGHT2_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT2_POSITION_Z&lt;br /&gt;
|-&lt;br /&gt;
| 0166&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_XY|GPUREG_LIGHT2_SPOTDIR_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT2_SPOT_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0167&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_Z|GPUREG_LIGHT2_SPOTDIR_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT2_SPOT_Z&lt;br /&gt;
|-&lt;br /&gt;
| 0168&lt;br /&gt;
| [[#GPUREG_0168|GPUREG_0168]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0169&lt;br /&gt;
| [[#GPUREG_LIGHTi_CONFIG|GPUREG_LIGHT2_CONFIG]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT2_TYPE&lt;br /&gt;
|-&lt;br /&gt;
| 016A&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_BIAS|GPUREG_LIGHT2_ATTENUATION_BIAS]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT2_DIST_ATTN_BIAS&lt;br /&gt;
|-&lt;br /&gt;
| 016B&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_SCALE|GPUREG_LIGHT2_ATTENUATION_SCALE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT2_DIST_ATTN_SCALE&lt;br /&gt;
|-&lt;br /&gt;
| 016C&lt;br /&gt;
| [[#GPUREG_016C|GPUREG_016C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 016D&lt;br /&gt;
| [[#GPUREG_016D|GPUREG_016D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 016E&lt;br /&gt;
| [[#GPUREG_016E|GPUREG_016E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 016F&lt;br /&gt;
| [[#GPUREG_016F|GPUREG_016F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0170&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR0|GPUREG_LIGHT3_SPECULAR0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT3_SPECULAR0&lt;br /&gt;
|-&lt;br /&gt;
| 0171&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR1|GPUREG_LIGHT3_SPECULAR1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT3_SPECULAR1&lt;br /&gt;
|-&lt;br /&gt;
| 0172&lt;br /&gt;
| [[#GPUREG_LIGHTi_DIFFUSE|GPUREG_LIGHT3_DIFFUSE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT3_DIFFUSE&lt;br /&gt;
|-&lt;br /&gt;
| 0173&lt;br /&gt;
| [[#GPUREG_LIGHTi_AMBIENT|GPUREG_LIGHT3_AMBIENT]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT3_AMBIENT&lt;br /&gt;
|-&lt;br /&gt;
| 0174&lt;br /&gt;
| [[#GPUREG_LIGHTi_XY|GPUREG_LIGHT3_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT3_POSITION_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0175&lt;br /&gt;
| [[#GPUREG_LIGHTi_Z|GPUREG_LIGHT3_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT3_POSITION_Z&lt;br /&gt;
|-&lt;br /&gt;
| 0176&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_XY|GPUREG_LIGHT3_SPOTDIR_XY]]&lt;br /&gt;
|? &lt;br /&gt;
|PICA_REG_FRAG_LIGHT3_SPOT_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0177&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_Z|GPUREG_LIGHT3_SPOTDIR_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT3_SPOT_Z&lt;br /&gt;
|-&lt;br /&gt;
| 0178&lt;br /&gt;
| [[#GPUREG_0178|GPUREG_0178]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0179&lt;br /&gt;
| [[#GPUREG_LIGHTi_CONFIG|GPUREG_LIGHT3_CONFIG]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT3_TYPE&lt;br /&gt;
|-&lt;br /&gt;
| 017A&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_BIAS|GPUREG_LIGHT3_ATTENUATION_BIAS]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT3_DIST_ATTN_BIAS&lt;br /&gt;
|-&lt;br /&gt;
| 017B&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_SCALE|GPUREG_LIGHT3_ATTENUATION_SCALE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT3_DIST_ATTN_SCALE&lt;br /&gt;
|-&lt;br /&gt;
| 017C&lt;br /&gt;
| [[#GPUREG_017C|GPUREG_017C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 017D&lt;br /&gt;
| [[#GPUREG_017D|GPUREG_017D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 017E&lt;br /&gt;
| [[#GPUREG_017E|GPUREG_017E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 017F&lt;br /&gt;
| [[#GPUREG_017F|GPUREG_017F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0180&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR0|GPUREG_LIGHT4_SPECULAR0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT4_SPECULAR0&lt;br /&gt;
|-&lt;br /&gt;
| 0181&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR1|GPUREG_LIGHT4_SPECULAR1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT4_SPECULAR1&lt;br /&gt;
|-&lt;br /&gt;
| 0182&lt;br /&gt;
| [[#GPUREG_LIGHTi_DIFFUSE|GPUREG_LIGHT4_DIFFUSE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT4_DIFFUSE&lt;br /&gt;
|-&lt;br /&gt;
| 0183&lt;br /&gt;
| [[#GPUREG_LIGHTi_AMBIENT|GPUREG_LIGHT4_AMBIENT]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT4_AMBIENT&lt;br /&gt;
|-&lt;br /&gt;
| 0184&lt;br /&gt;
| [[#GPUREG_LIGHTi_XY|GPUREG_LIGHT4_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT4_POSITION_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0185&lt;br /&gt;
| [[#GPUREG_LIGHTi_Z|GPUREG_LIGHT4_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT4_POSITION_Z&lt;br /&gt;
|-&lt;br /&gt;
| 0186&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_XY|GPUREG_LIGHT4_SPOTDIR_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT4_SPOT_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0187&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_Z|GPUREG_LIGHT4_SPOTDIR_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT4_SPOT_Z&lt;br /&gt;
|-&lt;br /&gt;
| 0188&lt;br /&gt;
| [[#GPUREG_0188|GPUREG_0188]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0189&lt;br /&gt;
| [[#GPUREG_LIGHTi_CONFIG|GPUREG_LIGHT4_CONFIG]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT4_TYPE&lt;br /&gt;
|-&lt;br /&gt;
| 018A&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_BIAS|GPUREG_LIGHT4_ATTENUATION_BIAS]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT4_DIST_ATTN_BIAS&lt;br /&gt;
|-&lt;br /&gt;
| 018B&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_SCALE|GPUREG_LIGHT4_ATTENUATION_SCALE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT4_DIST_ATTN_SCALE&lt;br /&gt;
|-&lt;br /&gt;
| 018C&lt;br /&gt;
| [[#GPUREG_018C|GPUREG_018C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 018D&lt;br /&gt;
| [[#GPUREG_018D|GPUREG_018D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 018E&lt;br /&gt;
| [[#GPUREG_018E|GPUREG_018E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 018F&lt;br /&gt;
| [[#GPUREG_018F|GPUREG_018F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0190&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR0|GPUREG_LIGHT5_SPECULAR0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT5_SPECULAR0&lt;br /&gt;
|-&lt;br /&gt;
| 0191&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR1|GPUREG_LIGHT5_SPECULAR1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT5_SPECULAR1&lt;br /&gt;
|-&lt;br /&gt;
| 0192&lt;br /&gt;
| [[#GPUREG_LIGHTi_DIFFUSE|GPUREG_LIGHT5_DIFFUSE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT5_DIFFUSE&lt;br /&gt;
|-&lt;br /&gt;
| 0193&lt;br /&gt;
| [[#GPUREG_LIGHTi_AMBIENT|GPUREG_LIGHT5_AMBIENT]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT5_AMBIENT&lt;br /&gt;
|-&lt;br /&gt;
| 0194&lt;br /&gt;
| [[#GPUREG_LIGHTi_XY|GPUREG_LIGHT5_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT5_POSITION_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0195&lt;br /&gt;
| [[#GPUREG_LIGHTi_Z|GPUREG_LIGHT5_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT5_POSITION_Z&lt;br /&gt;
|-&lt;br /&gt;
| 0196&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_XY|GPUREG_LIGHT5_SPOTDIR_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT5_SPOT_XY&lt;br /&gt;
|-&lt;br /&gt;
| 0197&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_Z|GPUREG_LIGHT5_SPOTDIR_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT5_SPOT_Z&lt;br /&gt;
|-&lt;br /&gt;
| 0198&lt;br /&gt;
| [[#GPUREG_0198|GPUREG_0198]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0199&lt;br /&gt;
| [[#GPUREG_LIGHTi_CONFIG|GPUREG_LIGHT5_CONFIG]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT5_TYPE&lt;br /&gt;
|-&lt;br /&gt;
| 019A&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_BIAS|GPUREG_LIGHT5_ATTENUATION_BIAS]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_FRAG_LIGHT5_DIST_ATTN_BIAS&lt;br /&gt;
|-&lt;br /&gt;
| 019B&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_SCALE|GPUREG_LIGHT5_ATTENUATION_SCALE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_FRAG_LIGHT5_DIST_ATTN_SCALE&lt;br /&gt;
|-&lt;br /&gt;
| 019C&lt;br /&gt;
| [[#GPUREG_019C|GPUREG_019C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 019D&lt;br /&gt;
| [[#GPUREG_019D|GPUREG_019D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 019E&lt;br /&gt;
| [[#GPUREG_019E|GPUREG_019E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 019F&lt;br /&gt;
| [[#GPUREG_019F|GPUREG_019F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01A0&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR0|GPUREG_LIGHT6_SPECULAR0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT6_SPECULAR0&lt;br /&gt;
|-&lt;br /&gt;
| 01A1&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR1|GPUREG_LIGHT6_SPECULAR1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT6_SPECULAR1&lt;br /&gt;
|-&lt;br /&gt;
| 01A2&lt;br /&gt;
| [[#GPUREG_LIGHTi_DIFFUSE|GPUREG_LIGHT6_DIFFUSE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_FRAG_LIGHT6_DIFFUSE&lt;br /&gt;
|-&lt;br /&gt;
| 01A3&lt;br /&gt;
| [[#GPUREG_LIGHTi_AMBIENT|GPUREG_LIGHT6_AMBIENT]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_FRAG_LIGHT6_AMBIENT&lt;br /&gt;
|-&lt;br /&gt;
| 01A4&lt;br /&gt;
| [[#GPUREG_LIGHTi_XY|GPUREG_LIGHT6_XY]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_FRAG_LIGHT6_POSITION_XY&lt;br /&gt;
|-&lt;br /&gt;
| 01A5&lt;br /&gt;
| [[#GPUREG_LIGHTi_Z|GPUREG_LIGHT6_Z]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_FRAG_LIGHT6_POSITION_Z&lt;br /&gt;
|-&lt;br /&gt;
| 01A6&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_XY|GPUREG_LIGHT6_SPOTDIR_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT6_SPOT_XY&lt;br /&gt;
|-&lt;br /&gt;
| 01A7&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_Z|GPUREG_LIGHT6_SPOTDIR_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT6_SPOT_Z&lt;br /&gt;
|-&lt;br /&gt;
| 01A8&lt;br /&gt;
| [[#GPUREG_01A8|GPUREG_01A8]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01A9&lt;br /&gt;
| [[#GPUREG_LIGHTi_CONFIG|GPUREG_LIGHT6_CONFIG]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT6_TYPE&lt;br /&gt;
|-&lt;br /&gt;
| 01AA&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_BIAS|GPUREG_LIGHT6_ATTENUATION_BIAS]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_FRAG_LIGHT6_DIST_ATTN_BIAS&lt;br /&gt;
|-&lt;br /&gt;
| 01AB&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_SCALE|GPUREG_LIGHT6_ATTENUATION_SCALE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_FRAG_LIGHT6_DIST_ATTN_SCALE&lt;br /&gt;
|-&lt;br /&gt;
| 01AC&lt;br /&gt;
| [[#GPUREG_01AC|GPUREG_01AC]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01AD&lt;br /&gt;
| [[#GPUREG_01AD|GPUREG_01AD]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01AE&lt;br /&gt;
| [[#GPUREG_01AE|GPUREG_01AE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01AF&lt;br /&gt;
| [[#GPUREG_01AF|GPUREG_01AF]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01B0&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR0|GPUREG_LIGHT7_SPECULAR0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT7_SPECULAR0&lt;br /&gt;
|-&lt;br /&gt;
| 01B1&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPECULAR1|GPUREG_LIGHT7_SPECULAR1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT7_SPECULAR1&lt;br /&gt;
|-&lt;br /&gt;
| 01B2&lt;br /&gt;
| [[#GPUREG_LIGHTi_DIFFUSE|GPUREG_LIGHT7_DIFFUSE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT7_DIFFUSE&lt;br /&gt;
|-&lt;br /&gt;
| 01B3&lt;br /&gt;
| [[#GPUREG_LIGHTi_AMBIENT|GPUREG_LIGHT7_AMBIENT]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT7_AMBIENT&lt;br /&gt;
|-&lt;br /&gt;
| 01B4&lt;br /&gt;
| [[#GPUREG_LIGHTi_XY|GPUREG_LIGHT7_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT7_POSITION_XY&lt;br /&gt;
|-&lt;br /&gt;
| 01B5&lt;br /&gt;
| [[#GPUREG_LIGHTi_Z|GPUREG_LIGHT7_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT7_POSITION_Z&lt;br /&gt;
|-&lt;br /&gt;
| 01B6&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_XY|GPUREG_LIGHT7_SPOTDIR_XY]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT7_SPOT_XY&lt;br /&gt;
|-&lt;br /&gt;
| 01B7&lt;br /&gt;
| [[#GPUREG_LIGHTi_SPOTDIR_Z|GPUREG_LIGHT7_SPOTDIR_Z]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT7_SPOT_Z&lt;br /&gt;
|-&lt;br /&gt;
| 01B8&lt;br /&gt;
| [[#GPUREG_01B8|GPUREG_01B8]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01B9&lt;br /&gt;
| [[#GPUREG_LIGHTi_CONFIG|GPUREG_LIGHT7_CONFIG]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT7_TYPE&lt;br /&gt;
|-&lt;br /&gt;
| 01BA&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_BIAS|GPUREG_LIGHT7_ATTENUATION_BIAS]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_FRAG_LIGHT7_DIST_ATTN_BIAS&lt;br /&gt;
|-&lt;br /&gt;
| 01BB&lt;br /&gt;
| [[#GPUREG_LIGHTi_ATTENUATION_SCALE|GPUREG_LIGHT7_ATTENUATION_SCALE]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_FRAG_LIGHT7_DIST_ATTN_SCALE&lt;br /&gt;
|-&lt;br /&gt;
| 01BC&lt;br /&gt;
| [[#GPUREG_01BC|GPUREG_01BC]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01BD&lt;br /&gt;
| [[#GPUREG_01BD|GPUREG_01BD]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01BE&lt;br /&gt;
| [[#GPUREG_01BE|GPUREG_01BE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01BF&lt;br /&gt;
| [[#GPUREG_01BF|GPUREG_01BF]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01C0&lt;br /&gt;
| [[#GPUREG_LIGHTING_AMBIENT|GPUREG_LIGHTING_AMBIENT]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_AMBIENT&lt;br /&gt;
|-&lt;br /&gt;
| 01C1&lt;br /&gt;
| [[#GPUREG_01C1|GPUREG_01C1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01C2&lt;br /&gt;
| [[#GPUREG_LIGHTING_NUM_LIGHTS|GPUREG_LIGHTING_NUM_LIGHTS]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_SRC_NUM&lt;br /&gt;
|-&lt;br /&gt;
| 01C3&lt;br /&gt;
| [[#GPUREG_LIGHTING_CONFIG0|GPUREG_LIGHTING_CONFIG0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_FUNC_MODE0&lt;br /&gt;
|-&lt;br /&gt;
| 01C4&lt;br /&gt;
| [[#GPUREG_LIGHTING_CONFIG1|GPUREG_LIGHTING_CONFIG1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_FUNC_MODE1&lt;br /&gt;
|-&lt;br /&gt;
| 01C5&lt;br /&gt;
| [[#GPUREG_LIGHTING_LUT_INDEX|GPUREG_LIGHTING_LUT_INDEX]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_LUT&lt;br /&gt;
|-&lt;br /&gt;
| 01C6&lt;br /&gt;
| [[#GPUREG_LIGHTING_ENABLE1|GPUREG_LIGHTING_ENABLE1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_EN1&lt;br /&gt;
|-&lt;br /&gt;
| 01C7&lt;br /&gt;
| [[#GPUREG_01C7|GPUREG_01C7]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01C8&lt;br /&gt;
| [[#GPUREG_LIGHTING_LUT_DATAi|GPUREG_LIGHTING_LUT_DATA0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_LUT_DATA0&lt;br /&gt;
|-&lt;br /&gt;
| 01C9&lt;br /&gt;
| [[#GPUREG_LIGHTING_LUT_DATAi|GPUREG_LIGHTING_LUT_DATA1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_LUT_DATA1&lt;br /&gt;
|-&lt;br /&gt;
| 01CA&lt;br /&gt;
| [[#GPUREG_LIGHTING_LUT_DATAi|GPUREG_LIGHTING_LUT_DATA2]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_LUT_DATA2&lt;br /&gt;
|-&lt;br /&gt;
| 01CB&lt;br /&gt;
| [[#GPUREG_LIGHTING_LUT_DATAi|GPUREG_LIGHTING_LUT_DATA3]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_LUT_DATA3&lt;br /&gt;
|-&lt;br /&gt;
| 01CC&lt;br /&gt;
| [[#GPUREG_LIGHTING_LUT_DATAi|GPUREG_LIGHTING_LUT_DATA4]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_LUT_DATA4&lt;br /&gt;
|-&lt;br /&gt;
| 01CD&lt;br /&gt;
| [[#GPUREG_LIGHTING_LUT_DATAi|GPUREG_LIGHTING_LUT_DATA5]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_LUT_DATA5&lt;br /&gt;
|-&lt;br /&gt;
| 01CE&lt;br /&gt;
| [[#GPUREG_LIGHTING_LUT_DATAi|GPUREG_LIGHTING_LUT_DATA6]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_LUT_DATA6&lt;br /&gt;
|-&lt;br /&gt;
| 01CF&lt;br /&gt;
| [[#GPUREG_LIGHTING_LUT_DATAi|GPUREG_LIGHTING_LUT_DATA7]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_LUT_DATA7&lt;br /&gt;
|-&lt;br /&gt;
| 01D0&lt;br /&gt;
| [[#GPUREG_LIGHTING_LUTINPUT_ABS|GPUREG_LIGHTING_LUTINPUT_ABS]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_ABSLUTINPUT&lt;br /&gt;
|-&lt;br /&gt;
| 01D1&lt;br /&gt;
| [[#GPUREG_LIGHTING_LUTINPUT_SELECT|GPUREG_LIGHTING_LUTINPUT_SELECT]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_LUTINPUT&lt;br /&gt;
|-&lt;br /&gt;
| 01D2&lt;br /&gt;
| [[#GPUREG_LIGHTING_LUTINPUT_SCALE|GPUREG_LIGHTING_LUTINPUT_SCALE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_LUTSCALE&lt;br /&gt;
|-&lt;br /&gt;
| 01D3&lt;br /&gt;
| [[#GPUREG_01D3|GPUREG_01D3]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01D4&lt;br /&gt;
| [[#GPUREG_01D4|GPUREG_01D4]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01D5&lt;br /&gt;
| [[#GPUREG_01D5|GPUREG_01D5]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01D6&lt;br /&gt;
| [[#GPUREG_01D6|GPUREG_01D6]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01D7&lt;br /&gt;
| [[#GPUREG_01D7|GPUREG_01D7]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01D8&lt;br /&gt;
| [[#GPUREG_01D8|GPUREG_01D8]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01D9&lt;br /&gt;
| [[#GPUREG_LIGHTING_LIGHT_PERMUTATION|GPUREG_LIGHTING_LIGHT_PERMUTATION]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_FRAG_LIGHT_SRC_EN_ID&lt;br /&gt;
|-&lt;br /&gt;
| 01DA&lt;br /&gt;
| [[#GPUREG_01DA|GPUREG_01DA]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01DB&lt;br /&gt;
| [[#GPUREG_01DB|GPUREG_01DB]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01DC&lt;br /&gt;
| [[#GPUREG_01DC|GPUREG_01DC]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01DD&lt;br /&gt;
| [[#GPUREG_01DD|GPUREG_01DD]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01DE&lt;br /&gt;
| [[#GPUREG_01DE|GPUREG_01DE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01DF&lt;br /&gt;
| [[#GPUREG_01DF|GPUREG_01DF]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01E0&lt;br /&gt;
| [[#GPUREG_01E0|GPUREG_01E0]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01E1&lt;br /&gt;
| [[#GPUREG_01E1|GPUREG_01E1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01E2&lt;br /&gt;
| [[#GPUREG_01E2|GPUREG_01E2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01E3&lt;br /&gt;
| [[#GPUREG_01E3|GPUREG_01E3]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01E4&lt;br /&gt;
| [[#GPUREG_01E4|GPUREG_01E4]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01E5&lt;br /&gt;
| [[#GPUREG_01E5|GPUREG_01E5]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01E6&lt;br /&gt;
| [[#GPUREG_01E6|GPUREG_01E6]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01E7&lt;br /&gt;
| [[#GPUREG_01E7|GPUREG_01E7]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01E8&lt;br /&gt;
| [[#GPUREG_01E8|GPUREG_01E8]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01E9&lt;br /&gt;
| [[#GPUREG_01E9|GPUREG_01E9]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01EA&lt;br /&gt;
| [[#GPUREG_01EA|GPUREG_01EA]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01EB&lt;br /&gt;
| [[#GPUREG_01EB|GPUREG_01EB]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01EC&lt;br /&gt;
| [[#GPUREG_01EC|GPUREG_01EC]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01ED&lt;br /&gt;
| [[#GPUREG_01ED|GPUREG_01ED]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01EE&lt;br /&gt;
| [[#GPUREG_01EE|GPUREG_01EE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01EF&lt;br /&gt;
| [[#GPUREG_01EF|GPUREG_01EF]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01F0&lt;br /&gt;
| [[#GPUREG_01F0|GPUREG_01F0]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01F1&lt;br /&gt;
| [[#GPUREG_01F1|GPUREG_01F1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01F2&lt;br /&gt;
| [[#GPUREG_01F2|GPUREG_01F2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01F3&lt;br /&gt;
| [[#GPUREG_01F3|GPUREG_01F3]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01F4&lt;br /&gt;
| [[#GPUREG_01F4|GPUREG_01F4]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01F5&lt;br /&gt;
| [[#GPUREG_01F5|GPUREG_01F5]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01F6&lt;br /&gt;
| [[#GPUREG_01F6|GPUREG_01F6]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01F7&lt;br /&gt;
| [[#GPUREG_01F7|GPUREG_01F7]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01F8&lt;br /&gt;
| [[#GPUREG_01F8|GPUREG_01F8]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01F9&lt;br /&gt;
| [[#GPUREG_01F9|GPUREG_01F9]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01FA&lt;br /&gt;
| [[#GPUREG_01FA|GPUREG_01FA]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01FB&lt;br /&gt;
| [[#GPUREG_01FB|GPUREG_01FB]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01FC&lt;br /&gt;
| [[#GPUREG_01FC|GPUREG_01FC]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01FD&lt;br /&gt;
| [[#GPUREG_01FD|GPUREG_01FD]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01FE&lt;br /&gt;
| [[#GPUREG_01FE|GPUREG_01FE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 01FF&lt;br /&gt;
| [[#GPUREG_01FF|GPUREG_01FF]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Geometry pipeline registers (0x200-0x27F) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Register ID&lt;br /&gt;
! Register name&lt;br /&gt;
! Notes&lt;br /&gt;
! Official Name&lt;br /&gt;
|-&lt;br /&gt;
| 0200&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERS_LOC|GPUREG_ATTRIBBUFFERS_LOC]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VTX_ATTR_ARRAYS_BASE_ADDR&lt;br /&gt;
|-&lt;br /&gt;
| 0201&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERS_FORMAT_LOW|GPUREG_ATTRIBBUFFERS_FORMAT_LOW]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VTX_ATTR_ARRAYS0&lt;br /&gt;
|-&lt;br /&gt;
| 0202&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERS_FORMAT_HIGH|GPUREG_ATTRIBBUFFERS_FORMAT_HIGH]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VTX_ATTR_ARRAYS1&lt;br /&gt;
|-&lt;br /&gt;
| 0203&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_OFFSET|GPUREG_ATTRIBBUFFER0_OFFSET]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_LOAD_ARRAY0_ATTR_OFFSET&lt;br /&gt;
|-&lt;br /&gt;
| 0204&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG1|GPUREG_ATTRIBBUFFER0_CONFIG1]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_LOAD_ARRAY0_ELEMENT0&lt;br /&gt;
|-&lt;br /&gt;
| 0205&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG2|GPUREG_ATTRIBBUFFER0_CONFIG2]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_LOAD_ARRAY0_ELEMENT1&lt;br /&gt;
|-&lt;br /&gt;
| 0206&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_OFFSET|GPUREG_ATTRIBBUFFER1_OFFSET]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0207&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG1|GPUREG_ATTRIBBUFFER1_CONFIG1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0208&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG2|GPUREG_ATTRIBBUFFER1_CONFIG2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0209&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_OFFSET|GPUREG_ATTRIBBUFFER2_OFFSET]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 020A&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG1|GPUREG_ATTRIBBUFFER2_CONFIG1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 020B&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG2|GPUREG_ATTRIBBUFFER2_CONFIG2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 020C&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_OFFSET|GPUREG_ATTRIBBUFFER3_OFFSET]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 020D&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG1|GPUREG_ATTRIBBUFFER3_CONFIG1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 020E&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG2|GPUREG_ATTRIBBUFFER3_CONFIG2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 020F&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_OFFSET|GPUREG_ATTRIBBUFFER4_OFFSET]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0210&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG1|GPUREG_ATTRIBBUFFER4_CONFIG1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0211&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG2|GPUREG_ATTRIBBUFFER4_CONFIG2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0212&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_OFFSET|GPUREG_ATTRIBBUFFER5_OFFSET]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0213&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG1|GPUREG_ATTRIBBUFFER5_CONFIG1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0214&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG2|GPUREG_ATTRIBBUFFER5_CONFIG2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0215&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_OFFSET|GPUREG_ATTRIBBUFFER6_OFFSET]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0216&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG1|GPUREG_ATTRIBBUFFER6_CONFIG1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0217&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG2|GPUREG_ATTRIBBUFFER6_CONFIG2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0218&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_OFFSET|GPUREG_ATTRIBBUFFER7_OFFSET]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0219&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG1|GPUREG_ATTRIBBUFFER7_CONFIG1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 021A&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG2|GPUREG_ATTRIBBUFFER7_CONFIG2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 021B&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_OFFSET|GPUREG_ATTRIBBUFFER8_OFFSET]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 021C&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG1|GPUREG_ATTRIBBUFFER8_CONFIG1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 021D&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG2|GPUREG_ATTRIBBUFFER8_CONFIG2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 021E&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_OFFSET|GPUREG_ATTRIBBUFFER9_OFFSET]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 021F&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG1|GPUREG_ATTRIBBUFFER9_CONFIG1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0220&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG2|GPUREG_ATTRIBBUFFER9_CONFIG2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0221&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_OFFSET|GPUREG_ATTRIBBUFFER10_OFFSET]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0222&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG1|GPUREG_ATTRIBBUFFER10_CONFIG1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0223&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG2|GPUREG_ATTRIBBUFFER10_CONFIG2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0224&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_OFFSET|GPUREG_ATTRIBBUFFER11_OFFSET]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0225&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG1|GPUREG_ATTRIBBUFFER11_CONFIG1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0226&lt;br /&gt;
| [[#GPUREG_ATTRIBBUFFERi_CONFIG2|GPUREG_ATTRIBBUFFER11_CONFIG2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0227&lt;br /&gt;
| [[#GPUREG_INDEXBUFFER_CONFIG|GPUREG_INDEXBUFFER_CONFIG]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_INDEX_ARRAY_ADDR_OFFSET&lt;br /&gt;
|-&lt;br /&gt;
| 0228&lt;br /&gt;
| [[#GPUREG_NUMVERTICES|GPUREG_NUMVERTICES]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_DRAW_VERTEX_NUM&lt;br /&gt;
|-&lt;br /&gt;
| 0229&lt;br /&gt;
| [[#GPUREG_GEOSTAGE_CONFIG|GPUREG_GEOSTAGE_CONFIG]]&lt;br /&gt;
| ?&lt;br /&gt;
|PICA_REG_DRAW_MODE0&lt;br /&gt;
|-&lt;br /&gt;
| 022A&lt;br /&gt;
| [[#GPUREG_VERTEX_OFFSET|GPUREG_VERTEX_OFFSET]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_DRAW_VERTEX_OFFSET&lt;br /&gt;
|-&lt;br /&gt;
| 022B&lt;br /&gt;
| [[#GPUREG_022B|GPUREG_022B]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 022C&lt;br /&gt;
| [[#GPUREG_022C|GPUREG_022C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 022D&lt;br /&gt;
| [[#GPUREG_POST_VERTEX_CACHE_NUM|GPUREG_POST_VERTEX_CACHE_NUM]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_POST_VERTEX_CACHE_NUM&lt;br /&gt;
|-&lt;br /&gt;
| 022E&lt;br /&gt;
| [[#GPUREG_DRAWARRAYS|GPUREG_DRAWARRAYS]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_START_DRAW_ARRAY&lt;br /&gt;
|-&lt;br /&gt;
| 022F&lt;br /&gt;
| [[#GPUREG_DRAWELEMENTS|GPUREG_DRAWELEMENTS]]&lt;br /&gt;
|&lt;br /&gt;
|PICA_REG_START_DRAW_ELEMENT&lt;br /&gt;
|-&lt;br /&gt;
| 0230&lt;br /&gt;
| [[#GPUREG_0230|GPUREG_0230]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0231&lt;br /&gt;
| [[#GPUREG_VTX_FUNC|GPUREG_VTX_FUNC]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_VTX_FUNC&lt;br /&gt;
|-&lt;br /&gt;
| 0232&lt;br /&gt;
| [[#GPUREG_FIXEDATTRIB_INDEX|GPUREG_FIXEDATTRIB_INDEX]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_VS_FIXED_ATTR&lt;br /&gt;
|-&lt;br /&gt;
| 0233&lt;br /&gt;
| [[#GPUREG_FIXEDATTRIB_DATAi|GPUREG_FIXEDATTRIB_DATA0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_VS_FIXED_ATTR_DATA0&lt;br /&gt;
|-&lt;br /&gt;
| 0234&lt;br /&gt;
| [[#GPUREG_FIXEDATTRIB_DATAi|GPUREG_FIXEDATTRIB_DATA1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_VS_FIXED_ATTR_DATA1&lt;br /&gt;
|-&lt;br /&gt;
| 0235&lt;br /&gt;
| [[#GPUREG_FIXEDATTRIB_DATAi|GPUREG_FIXEDATTRIB_DATA2]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_VS_FIXED_ATTR_DATA2&lt;br /&gt;
|-&lt;br /&gt;
| 0236&lt;br /&gt;
| [[#GPUREG_0236|GPUREG_0236]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0237&lt;br /&gt;
| [[#GPUREG_0237|GPUREG_0237]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0238&lt;br /&gt;
| [[#GPUREG_CMDBUF_SIZE0|GPUREG_CMDBUF_SIZE0]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_COMMAND_BUF_SIZE_CH0&lt;br /&gt;
|-&lt;br /&gt;
| 0239&lt;br /&gt;
| [[#GPUREG_CMDBUF_SIZE1|GPUREG_CMDBUF_SIZE1]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_COMMAND_BUF_SIZE_CH1&lt;br /&gt;
|-&lt;br /&gt;
| 023A&lt;br /&gt;
| [[#GPUREG_CMDBUF_ADDR0|GPUREG_CMDBUF_ADDR0]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_COMMAND_BUF_ADDR_CH0&lt;br /&gt;
|-&lt;br /&gt;
| 023B&lt;br /&gt;
| [[#GPUREG_CMDBUF_ADDR1|GPUREG_CMDBUF_ADDR1]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_COMMAND_BUF_ADDR_CH1&lt;br /&gt;
|-&lt;br /&gt;
| 023C&lt;br /&gt;
| [[#GPUREG_CMDBUF_JUMP0|GPUREG_CMDBUF_JUMP0]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_COMMAND_BUF_KICK_CH0&lt;br /&gt;
|-&lt;br /&gt;
| 023D&lt;br /&gt;
| [[#GPUREG_CMDBUF_JUMP1|GPUREG_CMDBUF_JUMP1]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_COMMAND_BUF_KICK_CH1&lt;br /&gt;
|-&lt;br /&gt;
| 023E&lt;br /&gt;
| [[#GPUREG_023E|GPUREG_023E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 023F&lt;br /&gt;
| [[#GPUREG_023F|GPUREG_023F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0240&lt;br /&gt;
| [[#GPUREG_0240|GPUREG_0240]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0241&lt;br /&gt;
| [[#GPUREG_0241|GPUREG_0241]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0242&lt;br /&gt;
| [[#GPUREG_VSH_NUM_ATTR|GPUREG_VSH_NUM_ATTR]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_VS_ATTR_NUM1&lt;br /&gt;
|-&lt;br /&gt;
| 0243&lt;br /&gt;
| [[#GPUREG_0243|GPUREG_0243]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0244&lt;br /&gt;
| [[#GPUREG_VSH_COM_MODE|GPUREG_VSH_COM_MODE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_VS_COM_MODE&lt;br /&gt;
|-&lt;br /&gt;
| 0245&lt;br /&gt;
| [[#GPUREG_START_DRAW_FUNC0|GPUREG_START_DRAW_FUNC0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_START_DRAW_FUNC0&lt;br /&gt;
|-&lt;br /&gt;
| 0246&lt;br /&gt;
| [[#GPUREG_0246|GPUREG_0246]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0247&lt;br /&gt;
| [[#GPUREG_0247|GPUREG_0247]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0248&lt;br /&gt;
| [[#GPUREG_0248|GPUREG_0248]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0249&lt;br /&gt;
| [[#GPUREG_0249|GPUREG_0249]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 024A&lt;br /&gt;
| [[#GPUREG_VSH_OUTMAP_TOTAL1|GPUREG_VSH_OUTMAP_TOTAL1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_VS_OUT_REG_NUM1&lt;br /&gt;
|-&lt;br /&gt;
| 024B&lt;br /&gt;
| [[#GPUREG_024B|GPUREG_024B]]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 024C&lt;br /&gt;
| [[#GPUREG_024C|GPUREG_024C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 024D&lt;br /&gt;
| [[#GPUREG_024D|GPUREG_024D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 024E&lt;br /&gt;
| [[#GPUREG_024E|GPUREG_024E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 024F&lt;br /&gt;
| [[#GPUREG_024F|GPUREG_024F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0250&lt;br /&gt;
| [[#GPUREG_0250|GPUREG_0250]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0251&lt;br /&gt;
| [[#GPUREG_VSH_OUTMAP_TOTAL2|GPUREG_VSH_OUTMAP_TOTAL2]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_VS_OUT_REG_NUM2&lt;br /&gt;
|-&lt;br /&gt;
| 0252&lt;br /&gt;
| [[#GPUREG_GSH_MISC0|GPUREG_GSH_MISC0]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_GS_MISC_REG0&lt;br /&gt;
|-&lt;br /&gt;
| 0253&lt;br /&gt;
| [[#GPUREG_GEOSTAGE_CONFIG2|GPUREG_GEOSTAGE_CONFIG2]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_DRAW_MODE1&lt;br /&gt;
|-&lt;br /&gt;
| 0254&lt;br /&gt;
| [[#GPUREG_GSH_MISC1|GPUREG_GSH_MISC1]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_GS_MISC_REG1&lt;br /&gt;
|-&lt;br /&gt;
| 0255&lt;br /&gt;
| [[#GPUREG_0255|GPUREG_0255]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0256&lt;br /&gt;
| [[#GPUREG_0256|GPUREG_0256]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0257&lt;br /&gt;
| [[#GPUREG_0257|GPUREG_0257]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0258&lt;br /&gt;
| [[#GPUREG_0258|GPUREG_0258]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0259&lt;br /&gt;
| [[#GPUREG_0259|GPUREG_0259]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 025A&lt;br /&gt;
| [[#GPUREG_025A|GPUREG_025A]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 025B&lt;br /&gt;
| [[#GPUREG_025B|GPUREG_025B]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 025C&lt;br /&gt;
| [[#GPUREG_025C|GPUREG_025C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 025D&lt;br /&gt;
| [[#GPUREG_025D|GPUREG_025D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 025E&lt;br /&gt;
| [[#GPUREG_PRIMITIVE_CONFIG|GPUREG_PRIMITIVE_CONFIG]]&lt;br /&gt;
| ?&lt;br /&gt;
|PICA_REG_GS_OUT_REG_NUM3 / PICA_REG_DRAW_MODE2 / PICA_REG_VS_OUT_REG_NUM3&lt;br /&gt;
|-&lt;br /&gt;
| 025F&lt;br /&gt;
| [[#GPUREG_RESTART_PRIMITIVE|GPUREG_RESTART_PRIMITIVE]]&lt;br /&gt;
|?&lt;br /&gt;
|PICA_REG_START_DRAW_FUNC1&lt;br /&gt;
|-&lt;br /&gt;
| 0260&lt;br /&gt;
| [[#GPUREG_0260|GPUREG_0260]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0261&lt;br /&gt;
| [[#GPUREG_0261|GPUREG_0261]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0262&lt;br /&gt;
| [[#GPUREG_0262|GPUREG_0262]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0263&lt;br /&gt;
| [[#GPUREG_0263|GPUREG_0263]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0264&lt;br /&gt;
| [[#GPUREG_0264|GPUREG_0264]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0265&lt;br /&gt;
| [[#GPUREG_0265|GPUREG_0265]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0266&lt;br /&gt;
| [[#GPUREG_0266|GPUREG_0266]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0267&lt;br /&gt;
| [[#GPUREG_0267|GPUREG_0267]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0268&lt;br /&gt;
| [[#GPUREG_0268|GPUREG_0268]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0269&lt;br /&gt;
| [[#GPUREG_0269|GPUREG_0269]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 026A&lt;br /&gt;
| [[#GPUREG_026A|GPUREG_026A]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 026B&lt;br /&gt;
| [[#GPUREG_026B|GPUREG_026B]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 026C&lt;br /&gt;
| [[#GPUREG_026C|GPUREG_026C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 026D&lt;br /&gt;
| [[#GPUREG_026D|GPUREG_026D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 026E&lt;br /&gt;
| [[#GPUREG_026E|GPUREG_026E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 026F&lt;br /&gt;
| [[#GPUREG_026F|GPUREG_026F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0270&lt;br /&gt;
| [[#GPUREG_0270|GPUREG_0270]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0271&lt;br /&gt;
| [[#GPUREG_0271|GPUREG_0271]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0272&lt;br /&gt;
| [[#GPUREG_0272|GPUREG_0272]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0273&lt;br /&gt;
| [[#GPUREG_0273|GPUREG_0273]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0274&lt;br /&gt;
| [[#GPUREG_0274|GPUREG_0274]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0275&lt;br /&gt;
| [[#GPUREG_0275|GPUREG_0275]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0276&lt;br /&gt;
| [[#GPUREG_0276|GPUREG_0276]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0277&lt;br /&gt;
| [[#GPUREG_0277|GPUREG_0277]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0278&lt;br /&gt;
| [[#GPUREG_0278|GPUREG_0278]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0279&lt;br /&gt;
| [[#GPUREG_0279|GPUREG_0279]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 027A&lt;br /&gt;
| [[#GPUREG_027A|GPUREG_027A]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 027B&lt;br /&gt;
| [[#GPUREG_027B|GPUREG_027B]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 027C&lt;br /&gt;
| [[#GPUREG_027C|GPUREG_027C]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 027D&lt;br /&gt;
| [[#GPUREG_027D|GPUREG_027D]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 027E&lt;br /&gt;
| [[#GPUREG_027E|GPUREG_027E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 027F&lt;br /&gt;
| [[#GPUREG_027F|GPUREG_027F]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Shader registers (0x280-0x2DF) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Register ID&lt;br /&gt;
! Register name&lt;br /&gt;
! Notes&lt;br /&gt;
! Official Name&lt;br /&gt;
|-&lt;br /&gt;
! colspan=4 | Geometry shader&lt;br /&gt;
|-&lt;br /&gt;
| 0280&lt;br /&gt;
| [[#GPUREG_SH_BOOLUNIFORM|GPUREG_GSH_BOOLUNIFORM]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_BOOL&lt;br /&gt;
|-&lt;br /&gt;
| 0281&lt;br /&gt;
| [[#GPUREG_SH_INTUNIFORM_Ii|GPUREG_GSH_INTUNIFORM_I0]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_INT0&lt;br /&gt;
|-&lt;br /&gt;
| 0282&lt;br /&gt;
| [[#GPUREG_SH_INTUNIFORM_Ii|GPUREG_GSH_INTUNIFORM_I1]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_INT1&lt;br /&gt;
|-&lt;br /&gt;
| 0283&lt;br /&gt;
| [[#GPUREG_SH_INTUNIFORM_Ii|GPUREG_GSH_INTUNIFORM_I2]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_INT2&lt;br /&gt;
|-&lt;br /&gt;
| 0284&lt;br /&gt;
| [[#GPUREG_SH_INTUNIFORM_Ii|GPUREG_GSH_INTUNIFORM_I3]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_INT3&lt;br /&gt;
|-&lt;br /&gt;
| 0285&lt;br /&gt;
| [[#GPUREG_0285|GPUREG_0285]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0286&lt;br /&gt;
| [[#GPUREG_0286|GPUREG_0286]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0287&lt;br /&gt;
| [[#GPUREG_0287|GPUREG_0287]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0288&lt;br /&gt;
| [[#GPUREG_0288|GPUREG_0288]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0289&lt;br /&gt;
| [[#GPUREG_SH_INPUTBUFFER_CONFIG|GPUREG_GSH_INPUTBUFFER_CONFIG]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_ATTR_NUM&lt;br /&gt;
|-&lt;br /&gt;
| 028A&lt;br /&gt;
| [[#GPUREG_SH_ENTRYPOINT|GPUREG_GSH_ENTRYPOINT]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_START_ADDR&lt;br /&gt;
|-&lt;br /&gt;
| 028B&lt;br /&gt;
| [[#GPUREG_SH_ATTRIBUTES_PERMUTATION_LOW|GPUREG_GSH_ATTRIBUTES_PERMUTATION_LOW]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_ATTR_IN_REG_MAP0&lt;br /&gt;
|-&lt;br /&gt;
| 028C&lt;br /&gt;
| [[#GPUREG_SH_ATTRIBUTES_PERMUTATION_HIGH|GPUREG_GSH_ATTRIBUTES_PERMUTATION_HIGH]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_ATTR_IN_REG_MAP1&lt;br /&gt;
|-&lt;br /&gt;
| 028D&lt;br /&gt;
| [[#GPUREG_SH_OUTMAP_MASK|GPUREG_GSH_OUTMAP_MASK]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_OUT_REG_MASK&lt;br /&gt;
|-&lt;br /&gt;
| 028E&lt;br /&gt;
| [[#GPUREG_028E|GPUREG_028E]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 028F&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_END|GPUREG_GSH_CODETRANSFER_END]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_RENEWAL_END&lt;br /&gt;
|-&lt;br /&gt;
| 0290&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_INDEX|GPUREG_GSH_FLOATUNIFORM_INDEX]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_FLOAT_ADDR&lt;br /&gt;
|-&lt;br /&gt;
| 0291&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_GSH_FLOATUNIFORM_DATA0]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_FLOAT_DATA0&lt;br /&gt;
|-&lt;br /&gt;
| 0292&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_GSH_FLOATUNIFORM_DATA1]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_FLOAT_DATA1&lt;br /&gt;
|-&lt;br /&gt;
| 0293&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_GSH_FLOATUNIFORM_DATA2]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_FLOAT_DATA2&lt;br /&gt;
|-&lt;br /&gt;
| 0294&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_GSH_FLOATUNIFORM_DATA3]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_FLOAT_DATA3&lt;br /&gt;
|-&lt;br /&gt;
| 0295&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_GSH_FLOATUNIFORM_DATA4]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_FLOAT_DATA4&lt;br /&gt;
|-&lt;br /&gt;
| 0296&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_GSH_FLOATUNIFORM_DATA5]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_FLOAT_DATA5&lt;br /&gt;
|-&lt;br /&gt;
| 0297&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_GSH_FLOATUNIFORM_DATA6]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_FLOAT_DATA6&lt;br /&gt;
|-&lt;br /&gt;
| 0298&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_GSH_FLOATUNIFORM_DATA7]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_FLOAT_DATA7&lt;br /&gt;
|-&lt;br /&gt;
| 0299&lt;br /&gt;
| [[#GPUREG_0299|GPUREG_0299]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 029A&lt;br /&gt;
| [[#GPUREG_029A|GPUREG_029A]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 029B&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_INDEX|GPUREG_GSH_CODETRANSFER_INDEX]]&lt;br /&gt;
| ?&lt;br /&gt;
|PICA_REG_GS_PROG_ADDR&lt;br /&gt;
|-&lt;br /&gt;
| 029C&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_GSH_CODETRANSFER_DATA0]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_DATA0&lt;br /&gt;
|-&lt;br /&gt;
| 029D&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_GSH_CODETRANSFER_DATA1]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_DATA1&lt;br /&gt;
|-&lt;br /&gt;
| 029E&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_GSH_CODETRANSFER_DATA2]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_DATA2&lt;br /&gt;
|-&lt;br /&gt;
| 029F&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_GSH_CODETRANSFER_DATA3]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_DATA3&lt;br /&gt;
|-&lt;br /&gt;
| 02A0&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_GSH_CODETRANSFER_DATA4]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_DATA4&lt;br /&gt;
|-&lt;br /&gt;
| 02A1&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_GSH_CODETRANSFER_DATA5]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_DATA5&lt;br /&gt;
|-&lt;br /&gt;
| 02A2&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_GSH_CODETRANSFER_DATA6]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_DATA6&lt;br /&gt;
|-&lt;br /&gt;
| 02A3&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_GSH_CODETRANSFER_DATA7]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_DATA7&lt;br /&gt;
|-&lt;br /&gt;
| 02A4&lt;br /&gt;
| [[#GPUREG_02A4|GPUREG_02A4]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02A5&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_INDEX|GPUREG_GSH_OPDESCS_INDEX]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_SWIZZLE_ADDR&lt;br /&gt;
|-&lt;br /&gt;
| 02A6&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_GSH_OPDESCS_DATA0]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_SWIZZLE_DATA0&lt;br /&gt;
|-&lt;br /&gt;
| 02A7&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_GSH_OPDESCS_DATA1]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_SWIZZLE_DATA1&lt;br /&gt;
|-&lt;br /&gt;
| 02A8&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_GSH_OPDESCS_DATA2]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_SWIZZLE_DATA2&lt;br /&gt;
|-&lt;br /&gt;
| 02A9&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_GSH_OPDESCS_DATA3]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_SWIZZLE_DATA3&lt;br /&gt;
|-&lt;br /&gt;
| 02AA&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_GSH_OPDESCS_DATA4]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_SWIZZLE_DATA4&lt;br /&gt;
|-&lt;br /&gt;
| 02AB&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_GSH_OPDESCS_DATA5]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_SWIZZLE_DATA5&lt;br /&gt;
|-&lt;br /&gt;
| 02AC&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_GSH_OPDESCS_DATA6]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_SWIZZLE_DATA6&lt;br /&gt;
|-&lt;br /&gt;
| 02AD&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_GSH_OPDESCS_DATA7]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_GS_PROG_SWIZZLE_DATA7&lt;br /&gt;
|-&lt;br /&gt;
| 02AE&lt;br /&gt;
| [[#GPUREG_02AE|GPUREG_02AE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02AF&lt;br /&gt;
| [[#GPUREG_02AF|GPUREG_02AF]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
! colspan=4 | Vertex shader&lt;br /&gt;
|-&lt;br /&gt;
| 02B0&lt;br /&gt;
| [[#GPUREG_SH_BOOLUNIFORM|GPUREG_VSH_BOOLUNIFORM]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_BOOL&lt;br /&gt;
|-&lt;br /&gt;
| 02B1&lt;br /&gt;
| [[#GPUREG_SH_INTUNIFORM_Ii|GPUREG_VSH_INTUNIFORM_I0]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_INT0&lt;br /&gt;
|-&lt;br /&gt;
| 02B2&lt;br /&gt;
| [[#GPUREG_SH_INTUNIFORM_Ii|GPUREG_VSH_INTUNIFORM_I1]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_INT1&lt;br /&gt;
|-&lt;br /&gt;
| 02B3&lt;br /&gt;
| [[#GPUREG_SH_INTUNIFORM_Ii|GPUREG_VSH_INTUNIFORM_I2]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_INT2&lt;br /&gt;
|-&lt;br /&gt;
| 02B4&lt;br /&gt;
| [[#GPUREG_SH_INTUNIFORM_Ii|GPUREG_VSH_INTUNIFORM_I3]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_INT3&lt;br /&gt;
|-&lt;br /&gt;
| 02B5&lt;br /&gt;
| [[#GPUREG_02B5|GPUREG_02B5]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02B6&lt;br /&gt;
| [[#GPUREG_02B6|GPUREG_02B6]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02B7&lt;br /&gt;
| [[#GPUREG_02B7|GPUREG_02B7]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02B8&lt;br /&gt;
| [[#GPUREG_02B8|GPUREG_02B8]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02B9&lt;br /&gt;
| [[#GPUREG_SH_INPUTBUFFER_CONFIG|GPUREG_VSH_INPUTBUFFER_CONFIG]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_ATTR_NUM0&lt;br /&gt;
|-&lt;br /&gt;
| 02BA&lt;br /&gt;
| [[#GPUREG_SH_ENTRYPOINT|GPUREG_VSH_ENTRYPOINT]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_START_ADDR&lt;br /&gt;
|-&lt;br /&gt;
| 02BB&lt;br /&gt;
| [[#GPUREG_SH_ATTRIBUTES_PERMUTATION_LOW|GPUREG_VSH_ATTRIBUTES_PERMUTATION_LOW]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_ATTR_IN_REG_MAP0&lt;br /&gt;
|-&lt;br /&gt;
| 02BC&lt;br /&gt;
| [[#GPUREG_SH_ATTRIBUTES_PERMUTATION_HIGH|GPUREG_VSH_ATTRIBUTES_PERMUTATION_HIGH]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_ATTR_IN_REG_MAP1&lt;br /&gt;
|-&lt;br /&gt;
| 02BD&lt;br /&gt;
| [[#GPUREG_SH_OUTMAP_MASK|GPUREG_VSH_OUTMAP_MASK]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_OUT_REG_MASK&lt;br /&gt;
|-&lt;br /&gt;
| 02BE&lt;br /&gt;
| [[#GPUREG_02BE|GPUREG_02BE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02BF&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_END|GPUREG_VSH_CODETRANSFER_END]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_RENEWAL_END&lt;br /&gt;
|-&lt;br /&gt;
| 02C0&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_INDEX|GPUREG_VSH_FLOATUNIFORM_INDEX]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_FLOAT_ADDR&lt;br /&gt;
|-&lt;br /&gt;
| 02C1&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_VSH_FLOATUNIFORM_DATA0]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_FLOAT_DATA0&lt;br /&gt;
|-&lt;br /&gt;
| 02C2&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_VSH_FLOATUNIFORM_DATA1]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_FLOAT_DATA1&lt;br /&gt;
|-&lt;br /&gt;
| 02C3&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_VSH_FLOATUNIFORM_DATA2]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_FLOAT_DATA2&lt;br /&gt;
|-&lt;br /&gt;
| 02C4&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_VSH_FLOATUNIFORM_DATA3]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_FLOAT_DATA3&lt;br /&gt;
|-&lt;br /&gt;
| 02C5&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_VSH_FLOATUNIFORM_DATA4]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_FLOAT_DATA4&lt;br /&gt;
|-&lt;br /&gt;
| 02C6&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_VSH_FLOATUNIFORM_DATA5]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_FLOAT_DATA5&lt;br /&gt;
|-&lt;br /&gt;
| 02C7&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_VSH_FLOATUNIFORM_DATA6]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_FLOAT_DATA6&lt;br /&gt;
|-&lt;br /&gt;
| 02C8&lt;br /&gt;
| [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_VSH_FLOATUNIFORM_DATA7]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_FLOAT_DATA7&lt;br /&gt;
|-&lt;br /&gt;
| 02C9&lt;br /&gt;
| [[#GPUREG_02C9|GPUREG_02C9]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02CA&lt;br /&gt;
| [[#GPUREG_02CA|GPUREG_02CA]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02CB&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_INDEX|GPUREG_VSH_CODETRANSFER_INDEX]]&lt;br /&gt;
| ?&lt;br /&gt;
|PICA_REG_VS_PROG_ADDR&lt;br /&gt;
|-&lt;br /&gt;
| 02CC&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_VSH_CODETRANSFER_DATA0]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_DATA0&lt;br /&gt;
|-&lt;br /&gt;
| 02CD&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_VSH_CODETRANSFER_DATA1]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_DATA1&lt;br /&gt;
|-&lt;br /&gt;
| 02CE&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_VSH_CODETRANSFER_DATA2]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_DATA2&lt;br /&gt;
|-&lt;br /&gt;
| 02CF&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_VSH_CODETRANSFER_DATA3]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_DATA3&lt;br /&gt;
|-&lt;br /&gt;
| 02D0&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_VSH_CODETRANSFER_DATA4]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_DATA4&lt;br /&gt;
|-&lt;br /&gt;
| 02D1&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_VSH_CODETRANSFER_DATA5]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_DATA5&lt;br /&gt;
|-&lt;br /&gt;
| 02D2&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_VSH_CODETRANSFER_DATA6]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_DATA6&lt;br /&gt;
|-&lt;br /&gt;
| 02D3&lt;br /&gt;
| [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_VSH_CODETRANSFER_DATA7]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_DATA7&lt;br /&gt;
|-&lt;br /&gt;
| 02D4&lt;br /&gt;
| [[#GPUREG_02D4|GPUREG_02D4]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02D5&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_INDEX|GPUREG_VSH_OPDESCS_INDEX]]&lt;br /&gt;
| ?&lt;br /&gt;
|PICA_REG_VS_PROG_SWIZZLE_ADDR&lt;br /&gt;
|-&lt;br /&gt;
| 02D6&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_VSH_OPDESCS_DATA0]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_SWIZZLE_DATA0&lt;br /&gt;
|-&lt;br /&gt;
| 02D7&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_VSH_OPDESCS_DATA1]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_SWIZZLE_DATA1&lt;br /&gt;
|-&lt;br /&gt;
| 02D8&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_VSH_OPDESCS_DATA2]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_SWIZZLE_DATA2&lt;br /&gt;
|-&lt;br /&gt;
| 02D9&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_VSH_OPDESCS_DATA3]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_SWIZZLE_DATA3&lt;br /&gt;
|-&lt;br /&gt;
| 02DA&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_VSH_OPDESCS_DATA4]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_SWIZZLE_DATA4&lt;br /&gt;
|-&lt;br /&gt;
| 02DB&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_VSH_OPDESCS_DATA5]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_SWIZZLE_DATA5&lt;br /&gt;
|-&lt;br /&gt;
| 02DC&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_VSH_OPDESCS_DATA6]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_SWIZZLE_DATA6&lt;br /&gt;
|-&lt;br /&gt;
| 02DD&lt;br /&gt;
| [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_VSH_OPDESCS_DATA7]]&lt;br /&gt;
| &lt;br /&gt;
|PICA_REG_VS_PROG_SWIZZLE_DATA7&lt;br /&gt;
|-&lt;br /&gt;
| 02DE&lt;br /&gt;
| [[#GPUREG_02DE|GPUREG_02DE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02DF&lt;br /&gt;
| [[#GPUREG_02DF|GPUREG_02DF]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Unknown registers (0x2E0-0x2FF) ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Register ID&lt;br /&gt;
! Register name&lt;br /&gt;
! Notes&lt;br /&gt;
! Official Name&lt;br /&gt;
|-&lt;br /&gt;
| 02E0&lt;br /&gt;
| [[#GPUREG_02E0|GPUREG_02E0]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02E1&lt;br /&gt;
| [[#GPUREG_02E1|GPUREG_02E1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02E2&lt;br /&gt;
| [[#GPUREG_02E2|GPUREG_02E2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02E3&lt;br /&gt;
| [[#GPUREG_02E3|GPUREG_02E3]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02E4&lt;br /&gt;
| [[#GPUREG_02E4|GPUREG_02E4]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02E5&lt;br /&gt;
| [[#GPUREG_02E5|GPUREG_02E5]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02E6&lt;br /&gt;
| [[#GPUREG_02E6|GPUREG_02E6]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02E7&lt;br /&gt;
| [[#GPUREG_02E7|GPUREG_02E7]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02E8&lt;br /&gt;
| [[#GPUREG_02E8|GPUREG_02E8]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02E9&lt;br /&gt;
| [[#GPUREG_02E9|GPUREG_02E9]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02EA&lt;br /&gt;
| [[#GPUREG_02EA|GPUREG_02EA]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02EB&lt;br /&gt;
| [[#GPUREG_02EB|GPUREG_02EB]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02EC&lt;br /&gt;
| [[#GPUREG_02EC|GPUREG_02EC]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02ED&lt;br /&gt;
| [[#GPUREG_02ED|GPUREG_02ED]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02EE&lt;br /&gt;
| [[#GPUREG_02EE|GPUREG_02EE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02EF&lt;br /&gt;
| [[#GPUREG_02EF|GPUREG_02EF]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02F0&lt;br /&gt;
| [[#GPUREG_02F0|GPUREG_02F0]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02F1&lt;br /&gt;
| [[#GPUREG_02F1|GPUREG_02F1]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02F2&lt;br /&gt;
| [[#GPUREG_02F2|GPUREG_02F2]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02F3&lt;br /&gt;
| [[#GPUREG_02F3|GPUREG_02F3]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02F4&lt;br /&gt;
| [[#GPUREG_02F4|GPUREG_02F4]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02F5&lt;br /&gt;
| [[#GPUREG_02F5|GPUREG_02F5]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02F6&lt;br /&gt;
| [[#GPUREG_02F6|GPUREG_02F6]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02F7&lt;br /&gt;
| [[#GPUREG_02F7|GPUREG_02F7]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02F8&lt;br /&gt;
| [[#GPUREG_02F8|GPUREG_02F8]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02F9&lt;br /&gt;
| [[#GPUREG_02F9|GPUREG_02F9]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02FA&lt;br /&gt;
| [[#GPUREG_02FA|GPUREG_02FA]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02FB&lt;br /&gt;
| [[#GPUREG_02FB|GPUREG_02FB]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02FC&lt;br /&gt;
| [[#GPUREG_02FC|GPUREG_02FC]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02FD&lt;br /&gt;
| [[#GPUREG_02FD|GPUREG_02FD]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02FE&lt;br /&gt;
| [[#GPUREG_02FE|GPUREG_02FE]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 02FF&lt;br /&gt;
| [[#GPUREG_02FF|GPUREG_02FF]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Miscellaneous registers ==&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_FINALIZE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| unsigned, Trigger P3D Interrupt (0 = idle, non-zero = trigger)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Writing to this register seems to signal the GPU to stop processing GPU commands from the current buffer; any command following a write to this register will be ignored. The value written to this register does not appear to matter, although 0x12345678 is the value typically written by commercial software.&lt;br /&gt;
Failure to write to this register in any command buffer will result in the GPU hanging.&lt;br /&gt;
&lt;br /&gt;
== Rasterizer registers ==&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_FACECULLING_CONFIG ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-1&lt;br /&gt;
| unsigned, Culling mode&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the face culling mode.&lt;br /&gt;
&lt;br /&gt;
Culling mode values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| None&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Front Face (Counter Clockwise)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Back Face (Counter Clockwise)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_VIEWPORT_WIDTH ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-23&lt;br /&gt;
| float1.7.16, width / 2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the viewport width, along with GPUREG_VIEWPORT_INVW.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_VIEWPORT_INVW ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1-31&lt;br /&gt;
| float1.7.23, 2 / width&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the viewport width, along with GPUREG_VIEWPORT_WIDTH.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_VIEWPORT_HEIGHT ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-23&lt;br /&gt;
| float1.7.16, height / 2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the viewport height, along with GPUREG_VIEWPORT_INVH.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_VIEWPORT_INVH ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1-31&lt;br /&gt;
| float1.7.23, 2 / height&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the viewport height, along with GPUREG_VIEWPORT_HEIGHT.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_FRAGOP_CLIP ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to enable clipping planes.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_FRAGOP_CLIP_DATA&#039;&#039;i&#039;&#039; ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-23&lt;br /&gt;
| float1.7.16, Clipping plane coefficient &#039;&#039;i&#039;&#039;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure clipping plane coefficients.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_DEPTHMAP_SCALE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-23&lt;br /&gt;
| float1.7.16, Near - Far&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the depth range scale.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_DEPTHMAP_OFFSET ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-23&lt;br /&gt;
| float1.7.16, Near + Polygon Offset&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the depth range bias.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_SH_OUTMAP_TOTAL ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-2&lt;br /&gt;
| unsigned, Number of following attributes&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the total shader output map attributes.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_SH_OUTMAP_O&#039;&#039;i&#039;&#039; ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-4&lt;br /&gt;
| unsigned, Semantic for the x component of the register.&lt;br /&gt;
|-&lt;br /&gt;
| 8-12&lt;br /&gt;
| unsigned, Semantic for the y component of the register.&lt;br /&gt;
|-&lt;br /&gt;
| 16-20&lt;br /&gt;
| unsigned, Semantic for the z component of the register.&lt;br /&gt;
|-&lt;br /&gt;
| 24-28&lt;br /&gt;
| unsigned, Semantic for the w component of the register.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers map components of the corresponding vertex shader output register to specific fixed-function semantics.&lt;br /&gt;
&lt;br /&gt;
Semantics that have not been mapped to a component of an output register have a value of 1&lt;br /&gt;
&lt;br /&gt;
Semantic values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Semantic&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| position.x&lt;br /&gt;
| rowspan=&amp;quot;4&amp;quot; | Vertex Position&lt;br /&gt;
|-&lt;br /&gt;
| 0x01&lt;br /&gt;
| position.y&lt;br /&gt;
|-&lt;br /&gt;
| 0x02&lt;br /&gt;
| position.z&lt;br /&gt;
|-&lt;br /&gt;
| 0x03&lt;br /&gt;
| position.w&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| normquat.x&lt;br /&gt;
| rowspan=&amp;quot;4&amp;quot; | Quaternion specifying the normal/tangent frame (for fragment lighting)&lt;br /&gt;
|-&lt;br /&gt;
| 0x05&lt;br /&gt;
| normquat.y&lt;br /&gt;
|-&lt;br /&gt;
| 0x06&lt;br /&gt;
| normquat.z&lt;br /&gt;
|-&lt;br /&gt;
| 0x07&lt;br /&gt;
| normquat.w&lt;br /&gt;
|-&lt;br /&gt;
| 0x08&lt;br /&gt;
| color.r&lt;br /&gt;
| rowspan=&amp;quot;4&amp;quot; | Vertex color&lt;br /&gt;
|-&lt;br /&gt;
| 0x09&lt;br /&gt;
| color.g&lt;br /&gt;
|-&lt;br /&gt;
| 0x0A&lt;br /&gt;
| color.b&lt;br /&gt;
|-&lt;br /&gt;
| 0x0B&lt;br /&gt;
| color.a&lt;br /&gt;
|-&lt;br /&gt;
| 0x0C&lt;br /&gt;
| texcoord0.u&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Texture coordinates for texture 0&lt;br /&gt;
|-&lt;br /&gt;
| 0x0D&lt;br /&gt;
| texcoord0.v&lt;br /&gt;
|-&lt;br /&gt;
| 0x0E&lt;br /&gt;
| texcoord1.u&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Texture coordinates for texture 1&lt;br /&gt;
|-&lt;br /&gt;
| 0x0F&lt;br /&gt;
| texcoord1.v&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| texcoord0.w&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| 0x12&lt;br /&gt;
| view.x&lt;br /&gt;
| rowspan=&amp;quot;3&amp;quot; | View vector (for fragment lighting)&lt;br /&gt;
|-&lt;br /&gt;
| 0x13&lt;br /&gt;
| view.y&lt;br /&gt;
|-&lt;br /&gt;
| 0x14&lt;br /&gt;
| view.z&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| 0x16&lt;br /&gt;
| texcoord2.u&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; | Texture coordinates for texture 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x17&lt;br /&gt;
| texcoord2.v&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| 0x1F&lt;br /&gt;
| Unused component&lt;br /&gt;
| Should be set for unused components of the output register&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_EARLYDEPTH_FUNC ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-1&lt;br /&gt;
| unsigned, Early depth function&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the early depth test function.&lt;br /&gt;
&lt;br /&gt;
Early depth function values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| &amp;gt;=&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| &amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| &amp;lt;=&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| &amp;lt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_EARLYDEPTH_TEST1 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets whether the early depth test is enabled.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_EARLYDEPTH_CLEAR ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Trigger (0 = idle, 1 = clear)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register triggers clearing the early depth data.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_SH_OUTATTR_MODE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Use texture coordinates (0 = don&#039;t use, 1 = use)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the shader output attribute mode.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_SCISSORTEST_MODE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-1&lt;br /&gt;
| unsigned, Enabled (0 = disabled, 3 = enabled)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to enable scissor testing.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_SCISSORTEST_POS ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-9&lt;br /&gt;
| unsigned, X1&lt;br /&gt;
|-&lt;br /&gt;
| 16-25&lt;br /&gt;
| unsigned, Y1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the scissor test start position.&lt;br /&gt;
&lt;br /&gt;
===	GPUREG_SCISSORTEST_DIM ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-9&lt;br /&gt;
| unsigned, X2&lt;br /&gt;
|-&lt;br /&gt;
| 16-25&lt;br /&gt;
| unsigned, Y2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the scissor test end position.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_VIEWPORT_XY ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-9&lt;br /&gt;
| signed, X&lt;br /&gt;
|-&lt;br /&gt;
| 16-25&lt;br /&gt;
| signed, Y&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the viewport position.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_EARLYDEPTH_DATA ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-23&lt;br /&gt;
| unsigned, Clear value&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the early depth clear value.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_DEPTHMAP_ENABLE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to enable depth range.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_RENDERBUF_DIM ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-10&lt;br /&gt;
| unsigned, Width&lt;br /&gt;
|-&lt;br /&gt;
| 12-21&lt;br /&gt;
| unsigned, Height - 1&lt;br /&gt;
|-&lt;br /&gt;
| 24&lt;br /&gt;
| 0x1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the output framebuffer dimensions.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_SH_OUTATTR_CLOCK ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, &#039;position.z&#039; present (0 = absent, 1 = present)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| unsigned, &#039;color&#039; component present (0 = absent, 1 = present)&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| unsigned, &#039;texcoord0&#039; component present (0 = absent, 1 = present)&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| unsigned, &#039;texcoord1&#039; component present (0 = absent, 1 = present)&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| unsigned, &#039;texcoord2&#039; component present (0 = absent, 1 = present)&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| unsigned, &#039;texcoord0.w&#039; present (0 = absent, 1 = present)&lt;br /&gt;
|-&lt;br /&gt;
| 24&lt;br /&gt;
| unsigned, &#039;normquat&#039; or &#039;view&#039; component present (0 = absent, 1 = present)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register controls the clock supply to parts relating to certain attributes.&lt;br /&gt;
&lt;br /&gt;
== Texturing registers ==&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_TEXUNIT_CONFIG ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Texture 0 enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| unsigned, Texture 1 enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| unsigned, Texture 2 enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0x0&lt;br /&gt;
|-&lt;br /&gt;
| 8-9&lt;br /&gt;
| unsigned, Texture 3 coordinates&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| unsigned, Texture 3 enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 12&lt;br /&gt;
| 0x1&lt;br /&gt;
|-&lt;br /&gt;
| 13&lt;br /&gt;
| unsigned, Texture 2 coordinates&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| unsigned, Clear texture cache (0 = don&#039;t clear, 1 = clear)&lt;br /&gt;
|-&lt;br /&gt;
| 17-31&lt;br /&gt;
| 0x0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to enable texture units.&lt;br /&gt;
&lt;br /&gt;
Texture 3 coordinates values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Texture 0&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Texture 1&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Texture 2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Texture 2 coordinates values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Texture 2&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Texture 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_TEXUNIT&#039;&#039;i&#039;&#039;_BORDER_COLOR ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Red&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| unsigned, Green&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| unsigned, Blue&lt;br /&gt;
|-&lt;br /&gt;
| 24-31&lt;br /&gt;
| unsigned, Alpha&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to set a texture unit&#039;s border color.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_TEXUNIT&#039;&#039;i&#039;&#039;_DIM ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-10&lt;br /&gt;
| unsigned, Height&lt;br /&gt;
|-&lt;br /&gt;
| 16-26&lt;br /&gt;
| unsigned, Width&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to set a texture unit&#039;s dimensions.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_TEXUNIT&#039;&#039;i&#039;&#039;_PARAM ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| unsigned, Magnification filter&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| unsigned, Minification filter&lt;br /&gt;
|-&lt;br /&gt;
| 4-5&lt;br /&gt;
| unsigned, ETC1 (0 = not ETC1, 2 = ETC1) note: still 0 for ETC1A4&lt;br /&gt;
|-&lt;br /&gt;
| 8-10&lt;br /&gt;
| unsigned, Wrap T&lt;br /&gt;
|-&lt;br /&gt;
| 12-14&lt;br /&gt;
| unsigned, Wrap S&lt;br /&gt;
|-&lt;br /&gt;
| 16-17&lt;br /&gt;
| 0x0&lt;br /&gt;
|-&lt;br /&gt;
| 20&lt;br /&gt;
| unsigned, Shadow (Texture 0 only, 0 = not shadow, 1 = shadow)&lt;br /&gt;
|-&lt;br /&gt;
| 24&lt;br /&gt;
| unsigned, Mipmap filter&lt;br /&gt;
|-&lt;br /&gt;
| 28-30&lt;br /&gt;
| unsigned, Type (Texture 0 only)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to set a texture unit&#039;s extra parameters.&lt;br /&gt;
&lt;br /&gt;
Filter values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Nearest&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Linear&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Wrap values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Clamp to edge&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Clamp to border&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Repeat&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Mirrored repeat&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Type values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| 2D&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Cube map&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Shadow 2D&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Projection&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Shadow cube&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Disabled&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_TEXUNIT&#039;&#039;i&#039;&#039;_LOD ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-12&lt;br /&gt;
| fixed1.4.8, Bias&lt;br /&gt;
|-&lt;br /&gt;
| 16-19&lt;br /&gt;
| unsigned, Max Level&lt;br /&gt;
|-&lt;br /&gt;
| 24-27&lt;br /&gt;
| unsigned, Min Level&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure a texture unit&#039;s level of detail.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_TEXUNIT&#039;&#039;i&#039;&#039;_ADDR&#039;&#039;i&#039;&#039; ===&lt;br /&gt;
&lt;br /&gt;
First ADDR register:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-27&lt;br /&gt;
| unsigned, Texture physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Subsequent ADDR registers:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-21&lt;br /&gt;
| unsigned, Texture physical address &amp;gt;&amp;gt; 3 (upper 6 bits reused from first ADDR register)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to set a texture unit&#039;s physical address(es) in memory. Individual texels in a texture are laid out in memory as a [http://en.wikipedia.org/wiki/Z-order_curve Z-order curve]. Mipmap data is stored directly following the main texture data.&lt;br /&gt;
&lt;br /&gt;
If the texture is a cube:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Register&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| ADDR1&lt;br /&gt;
| Positive X&lt;br /&gt;
|-&lt;br /&gt;
| ADDR2&lt;br /&gt;
| Negative X&lt;br /&gt;
|-&lt;br /&gt;
| ADDR3&lt;br /&gt;
| Positive Y&lt;br /&gt;
|-&lt;br /&gt;
| ADDR4&lt;br /&gt;
| Negative Y&lt;br /&gt;
|-&lt;br /&gt;
| ADDR5&lt;br /&gt;
| Positive Z&lt;br /&gt;
|-&lt;br /&gt;
| ADDR6&lt;br /&gt;
| Negative Z&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Otherwise, ADDR(1) points to a 2D texture, and the rest are empty. Addresses shall be ordered from lowest to highest, otherwise some faces might not render correctly.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_TEXUNIT&#039;&#039;i&#039;&#039;_SHADOW ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Perspective (0 = perspective, 1 = not perspective)&lt;br /&gt;
|-&lt;br /&gt;
| 1-23&lt;br /&gt;
| fixed0.0.24, Z bias (upper 23 bits)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to set a texture unit&#039;s shadow texture properties.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_TEXUNIT&#039;&#039;i&#039;&#039;_TYPE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, Format&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to set a texture unit&#039;s data format.&lt;br /&gt;
&lt;br /&gt;
Format values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Value&lt;br /&gt;
!  Description&lt;br /&gt;
!  GL Format&lt;br /&gt;
!  GL Data Type&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| RGBA8888&lt;br /&gt;
| GL_RGBA&lt;br /&gt;
| GL_UNSIGNED_BYTE&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| RGB888&lt;br /&gt;
| GL_RGB&lt;br /&gt;
| GL_UNSIGNED_BYTE&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| RGBA5551&lt;br /&gt;
| GL_RGBA&lt;br /&gt;
| GL_UNSIGNED_SHORT_5_5_5_1&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| RGB565&lt;br /&gt;
| GL_RGB&lt;br /&gt;
| GL_UNSIGNED_SHORT_5_6_5&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| RGBA4444&lt;br /&gt;
| GL_RGBA&lt;br /&gt;
| GL_UNSIGNED_SHORT_4_4_4_4&lt;br /&gt;
|-&lt;br /&gt;
| 0x5&lt;br /&gt;
| IA8&lt;br /&gt;
| GL_LUMINANCE_ALPHA&lt;br /&gt;
| GL_UNSIGNED_BYTE&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| HILO8&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| 0x7&lt;br /&gt;
| I8&lt;br /&gt;
| GL_LUMINANCE&lt;br /&gt;
| GL_UNSIGNED_BYTE&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| A8&lt;br /&gt;
| GL_ALPHA&lt;br /&gt;
| GL_UNSIGNED_BYTE&lt;br /&gt;
|-&lt;br /&gt;
| 0x9&lt;br /&gt;
| IA44&lt;br /&gt;
| GL_LUMINANCE_ALPHA&lt;br /&gt;
| GL_UNSIGNED_BYTE_4_4_EXT&lt;br /&gt;
|-&lt;br /&gt;
| 0xA&lt;br /&gt;
| I4&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0xB&lt;br /&gt;
| A4&lt;br /&gt;
| GL_ALPHA&lt;br /&gt;
| GL_UNSIGNED_NIBBLE_EXT&lt;br /&gt;
|-&lt;br /&gt;
| 0xC&lt;br /&gt;
| ETC1&lt;br /&gt;
| GL_ETC1_RGB8_OES&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| 0xD&lt;br /&gt;
| ETC1A4&lt;br /&gt;
|&lt;br /&gt;
| &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_LIGHTING_ENABLE0 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to enable lighting.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_TEXUNIT3_PROCTEX0 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-2&lt;br /&gt;
| unsigned, U-direction clamp&lt;br /&gt;
|-&lt;br /&gt;
| 3-5&lt;br /&gt;
| unsigned, V-direction clamp&lt;br /&gt;
|-&lt;br /&gt;
| 6-9&lt;br /&gt;
| unsigned, RGB mapping function&lt;br /&gt;
|-&lt;br /&gt;
| 10-13&lt;br /&gt;
| unsigned, Alpha mapping function&lt;br /&gt;
|-&lt;br /&gt;
| 14&lt;br /&gt;
| unsigned, Handle alpha separately (0 = don&#039;t separate, 1 = separate)&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| unsigned, Noise enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 16-17&lt;br /&gt;
| unsigned, U-direction shift&lt;br /&gt;
|-&lt;br /&gt;
| 18-19&lt;br /&gt;
| unsigned, V-direction shift&lt;br /&gt;
|-&lt;br /&gt;
| 20-27&lt;br /&gt;
| float1.5.10, Texture bias (lower 8 bits)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the procedural texture unit.&lt;br /&gt;
&lt;br /&gt;
Clamp values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Clamp to zero&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Clamp to edge&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Symmetrical repeat&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Mirrored repeat&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Pulse&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Mapping function values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| U&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| U²&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| V&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| V²&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| (U + V) / 2&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| (U² + V²) / 2&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| sqrt(U² + V²)&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Minimum&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| Maximum&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| Rmax&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Shift values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| None&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Odd&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Even&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_TEXUNIT3_PROCTEX1 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-15&lt;br /&gt;
| fixed1.3.12, U-direction noise amplitude&lt;br /&gt;
|-&lt;br /&gt;
| 16-31&lt;br /&gt;
| float1.5.10, U-direction noise phase&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the procedural texture unit&#039;s U-direction noise amplitude/phase.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_TEXUNIT3_PROCTEX2 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-15&lt;br /&gt;
| fixed1.3.12, V-direction noise amplitude&lt;br /&gt;
|-&lt;br /&gt;
| 16-31&lt;br /&gt;
| float1.5.10, V-direction noise phase&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the procedural texture unit&#039;s V-direction noise amplitude/phase.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_TEXUNIT3_PROCTEX3 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-15&lt;br /&gt;
| float1.5.10, U-direction noise frequency&lt;br /&gt;
|-&lt;br /&gt;
| 16-31&lt;br /&gt;
| float1.5.10, V-direction noise frequency&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the procedural texture unit&#039;s U-direction and V-direction noise frequency.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_TEXUNIT3_PROCTEX4 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-2&lt;br /&gt;
| unsigned, Minification filter&lt;br /&gt;
|-&lt;br /&gt;
| 3-6&lt;br /&gt;
| Min LOD (usually 0)&lt;br /&gt;
|-&lt;br /&gt;
| 7-10&lt;br /&gt;
| Max LOD (usually 6)&lt;br /&gt;
|-&lt;br /&gt;
| 11-18&lt;br /&gt;
| unsigned, Texture width&lt;br /&gt;
|-&lt;br /&gt;
| 19-26&lt;br /&gt;
| float1.5.10, Texture bias (upper 8 bits)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the procedural texture unit.&lt;br /&gt;
&lt;br /&gt;
Minification filter values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Nearest&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Linear&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Nearest, Mipmap Nearest&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Linear, Mipmap Nearest&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Nearest, Mipmap Linear&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Linear, Mipmap Linear&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_TEXUNIT3_PROCTEX5 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Texture offset (Mipmap level 0 / base level)&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| unsigned, mipmap level 1 offset (usually 0x80)&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| unsigned, mipmap level 2 offset (usually 0xC0)&lt;br /&gt;
|-&lt;br /&gt;
| 24-31&lt;br /&gt;
| unsigned, mipmap level 3 offset (usually 0xE0)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to set the procedural texture unit&#039;s offset. Mipmap level 4-7 seems to be hardcoded at offset 0xF0, 0xF8, 0xFC and 0xFE .&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_PROCTEX_LUT ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Index&lt;br /&gt;
|-&lt;br /&gt;
| 8-11&lt;br /&gt;
| unsigned, Reference table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to set which look-up table to write to, with GPUREG_PROCTEX_LUT_DATA&#039;&#039;i&#039;&#039;, at what index.&lt;br /&gt;
&lt;br /&gt;
Reference table values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Noise table&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| RGB mapping function table&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Alpha mapping function table&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Color table&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Color difference table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_PROCTEX_LUT_DATA&#039;&#039;i&#039;&#039; ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| LUT data&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers behave as a FIFO queue. Each write to these registers writes the provided value to the table selected with GPUREG_PROCTEX_LUT, starting at the selected index.&lt;br /&gt;
&lt;br /&gt;
==== Noise Table ====&lt;br /&gt;
&lt;br /&gt;
128 elements:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-11&lt;br /&gt;
| fixed0.0.12, Value&lt;br /&gt;
|-&lt;br /&gt;
| 12-23&lt;br /&gt;
| fixed0.0.12 with two&#039;s complement ( [0.5,1.0) mapped to [-1.0,0) ), Difference from next element&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== RGB Mapping Function Table ====&lt;br /&gt;
&lt;br /&gt;
128 elements:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-11&lt;br /&gt;
| fixed0.0.12, Value&lt;br /&gt;
|-&lt;br /&gt;
| 12-23&lt;br /&gt;
| fixed0.0.12 with two&#039;s complement, Difference from next element&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Alpha Mapping Function Table ====&lt;br /&gt;
&lt;br /&gt;
128 elements:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-11&lt;br /&gt;
| fixed0.0.12, Value&lt;br /&gt;
|-&lt;br /&gt;
| 12-23&lt;br /&gt;
| fixed0.0.12 with two&#039;s complement, Difference from next element&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Color Table ====&lt;br /&gt;
&lt;br /&gt;
256 elements:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Red&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| unsigned, Green&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| unsigned, Blue&lt;br /&gt;
|-&lt;br /&gt;
| 24-31&lt;br /&gt;
| unsigned, Alpha&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Color Difference Table ====&lt;br /&gt;
&lt;br /&gt;
256 elements:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| signed, Half of red difference between current and next color table elements&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| signed, Half of green difference between current and next color table elements&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| signed, Half of blue difference between current and next color table elements&lt;br /&gt;
|-&lt;br /&gt;
| 24-31&lt;br /&gt;
| signed, Half of alpha difference between current and next color table elements&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_TEXENV&#039;&#039;i&#039;&#039;_SOURCE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, RGB source 0&lt;br /&gt;
|-&lt;br /&gt;
| 4-7&lt;br /&gt;
| unsigned, RGB source 1&lt;br /&gt;
|-&lt;br /&gt;
| 8-11&lt;br /&gt;
| unsigned, RGB source 2&lt;br /&gt;
|-&lt;br /&gt;
| 16-19&lt;br /&gt;
| unsigned, Alpha source 0&lt;br /&gt;
|-&lt;br /&gt;
| 20-23&lt;br /&gt;
| unsigned, Alpha source 1&lt;br /&gt;
|-&lt;br /&gt;
| 24-27&lt;br /&gt;
| unsigned, Alpha source 2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures a texture combiner&#039;s sources.&lt;br /&gt;
&lt;br /&gt;
Source values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Primary color&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Fragment primary color&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Fragment secondary color&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Texture 0&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Texture 1&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Texture 2&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Texture 3&lt;br /&gt;
|-&lt;br /&gt;
| 13&lt;br /&gt;
| Previous buffer&lt;br /&gt;
|-&lt;br /&gt;
| 14&lt;br /&gt;
| Constant (from GPUREG_TEXENV&#039;&#039;i&#039;&#039;_COLOR)&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| Previous&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Using Previous (15) as a source in the first TEV stage returns the value of source 3. If source 3 has Previous it returns zero. Previous buffer (13) always returns zero.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_TEXENV&#039;&#039;i&#039;&#039;_OPERAND ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, RGB operand 0&lt;br /&gt;
|-&lt;br /&gt;
| 4-7&lt;br /&gt;
| unsigned, RGB operand 1&lt;br /&gt;
|-&lt;br /&gt;
| 8-11&lt;br /&gt;
| unsigned, RGB operand 2&lt;br /&gt;
|-&lt;br /&gt;
| 12-14&lt;br /&gt;
| unsigned, Alpha operand 0&lt;br /&gt;
|-&lt;br /&gt;
| 16-18&lt;br /&gt;
| unsigned, Alpha operand 1&lt;br /&gt;
|-&lt;br /&gt;
| 20-22&lt;br /&gt;
| unsigned, Alpha operand 2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures a texture combiner&#039;s operands.&lt;br /&gt;
&lt;br /&gt;
RGB operand values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Source color&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| One minus source color&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Source alpha&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| One minus source alpha&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Source red&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| One minus source red&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| Source green&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| One minus source green&lt;br /&gt;
|-&lt;br /&gt;
| 12&lt;br /&gt;
| Source blue&lt;br /&gt;
|-&lt;br /&gt;
| 13&lt;br /&gt;
| One minus source blue&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Alpha operand values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Source alpha&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| One minus source alpha&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Source red&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| One minus source red&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Source green&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| One minus source green&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Source blue&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| One minus source blue&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_TEXENV&#039;&#039;i&#039;&#039;_COMBINER ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, RGB combine&lt;br /&gt;
|-&lt;br /&gt;
| 16-19&lt;br /&gt;
| unsigned, Alpha combine&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures a texture combiner&#039;s combine mode.&lt;br /&gt;
&lt;br /&gt;
Combine values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Replace&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Modulate&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Add&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Add signed&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Interpolate&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Subtract&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Dot3 RGB&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Dot3 RGBA&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| Multiply then add&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| Add then multiply&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_TEXENV&#039;&#039;i&#039;&#039;_COLOR ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Red&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| unsigned, Green&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| unsigned, Blue&lt;br /&gt;
|-&lt;br /&gt;
| 24-31&lt;br /&gt;
| unsigned, Alpha&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures a texture combiner&#039;s constant color.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_TEXENV&#039;&#039;i&#039;&#039;_SCALE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-1&lt;br /&gt;
| unsigned, RGB scale&lt;br /&gt;
|-&lt;br /&gt;
| 16-17&lt;br /&gt;
| unsigned, Alpha scale&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures a texture combiner&#039;s scale value.&lt;br /&gt;
&lt;br /&gt;
Scale values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| 1x&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 2x&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 4x&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_TEXENV_UPDATE_BUFFER ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-2&lt;br /&gt;
| unsigned, Fog mode&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| unsigned, Shading density source&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| unsigned, TexEnv 1 RGB buffer input&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| unsigned, TexEnv 2 RGB buffer input&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| unsigned, TexEnv 3 RGB buffer input&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| unsigned, TexEnv 4 RGB buffer input&lt;br /&gt;
|-&lt;br /&gt;
| 12&lt;br /&gt;
| unsigned, TexEnv 1 alpha buffer input&lt;br /&gt;
|-&lt;br /&gt;
| 13&lt;br /&gt;
| unsigned, TexEnv 2 alpha buffer input&lt;br /&gt;
|-&lt;br /&gt;
| 14&lt;br /&gt;
| unsigned, TexEnv 3 alpha buffer input&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| unsigned, TexEnv 4 alpha buffer input&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| unsigned, Z flip (0 = don&#039;t flip, 1 = flip)&lt;br /&gt;
|-&lt;br /&gt;
| 24-25&lt;br /&gt;
| 0x0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is shared between the gas/fog mode configuration and texture combiner buffer inputs. Texture combiner buffer inputs are typically written with a mask of 0x2, and the gas/fog mode configuration is typically written with a mask of 0x5.&lt;br /&gt;
&lt;br /&gt;
Fog mode values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Disabled&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Fog&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Gas&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Shading density source values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Plain density&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Depth density&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Buffer input values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Previous buffer&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Previous&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_FOG_COLOR ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Red&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| unsigned, Green&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| unsigned, Blue&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the color of fog.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_GAS_ATTENUATION ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-15&lt;br /&gt;
| float1.5.10, Gas density attenuation&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the gas density attenuation.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_GAS_ACCMAX ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-15&lt;br /&gt;
| float1.5.10, Gas maximum density accumulation&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the gas maximum density accumulation.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_FOG_LUT_INDEX ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-15&lt;br /&gt;
| unsigned, Index&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to set what index to write to with GPUREG_FOG_LUT_DATA&#039;&#039;i&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_FOG_LUT_DATA&#039;&#039;i&#039;&#039; ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-23&lt;br /&gt;
| LUT data&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers behave as a FIFO queue. Each write to these registers writes the provided value to the fog look-up table, starting at the index selected with GPUREG_FOG_LUT_INDEX.&lt;br /&gt;
&lt;br /&gt;
==== Fog Look-Up Table ====&lt;br /&gt;
&lt;br /&gt;
128 elements:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-12&lt;br /&gt;
| fixed1.1.11, Difference from next element &lt;br /&gt;
|-&lt;br /&gt;
| 13-23&lt;br /&gt;
| fixed0.0.11, Value&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_TEXENV_BUFFER_COLOR ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Red&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| unsigned, Green&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| unsigned, Blue&lt;br /&gt;
|-&lt;br /&gt;
| 24-31&lt;br /&gt;
| unsigned, Alpha&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the texture combiner buffer color.&lt;br /&gt;
&lt;br /&gt;
== Framebuffer registers ==&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_COLOR_OPERATION ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-1&lt;br /&gt;
| unsigned, Fragment operation mode&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| unsigned, Blend mode&lt;br /&gt;
|-&lt;br /&gt;
| 16-25&lt;br /&gt;
| 0x0E4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the fragment operation mode and whether to use logic ops or blending.&lt;br /&gt;
&lt;br /&gt;
Fragment operation mode values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Default&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Gas&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Shadow&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Blend mode values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Logic op&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Blend&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_BLEND_FUNC ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-2&lt;br /&gt;
| unsigned, RGB equation&lt;br /&gt;
|-&lt;br /&gt;
| 8-10&lt;br /&gt;
| unsigned, Alpha equation&lt;br /&gt;
|-&lt;br /&gt;
| 16-19&lt;br /&gt;
| unsigned, RGB source function&lt;br /&gt;
|-&lt;br /&gt;
| 20-23&lt;br /&gt;
| unsigned, RGB destination function&lt;br /&gt;
|-&lt;br /&gt;
| 24-27&lt;br /&gt;
| unsigned, Alpha source function&lt;br /&gt;
|-&lt;br /&gt;
| 28-31&lt;br /&gt;
| unsigned, Alpha destination function&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the blending function.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equation values:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Add&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Subtract&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Reverse subtract&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Minimum&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Maximum&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Blend equations 5, 6, 7 appear to behave the same as blend equation 0 (Add)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function values:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Zero&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| One&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Source color&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| One minus source color&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Destination color&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| One minus destination color&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Source alpha&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| One minus source alpha&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| Destination alpha&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| One minus destination alpha&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| Constant color&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| One minus constant color&lt;br /&gt;
|-&lt;br /&gt;
| 12&lt;br /&gt;
| Constant alpha&lt;br /&gt;
|-&lt;br /&gt;
| 13&lt;br /&gt;
| One minus constant alpha&lt;br /&gt;
|-&lt;br /&gt;
| 14&lt;br /&gt;
| Source alpha saturate&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LOGIC_OP ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, Logic op&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the logic op.&lt;br /&gt;
&lt;br /&gt;
Logic op values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Clear&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| AND&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Reverse AND&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Copy&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Set&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Inverted copy&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Noop&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Invert&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| NAND&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| OR&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| NOR&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| XOR&lt;br /&gt;
|-&lt;br /&gt;
| 12&lt;br /&gt;
| Equivalent&lt;br /&gt;
|-&lt;br /&gt;
| 13&lt;br /&gt;
| Inverted AND&lt;br /&gt;
|-&lt;br /&gt;
| 14&lt;br /&gt;
| Reverse OR&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| Inverted OR&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_BLEND_COLOR ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Red&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| unsigned, Green&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| unsigned, Blue&lt;br /&gt;
|-&lt;br /&gt;
| 24-31&lt;br /&gt;
| unsigned, Alpha&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the blending color.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_FRAGOP_ALPHA_TEST ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 4-6&lt;br /&gt;
| unsigned, Function&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| unsigned, Reference value&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure alpha testing.&lt;br /&gt;
&lt;br /&gt;
Function values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Never&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Always&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Equal&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Not equal&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Less than&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Less than or equal&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Greater than&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Greater than or equal&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_STENCIL_TEST ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 4-6&lt;br /&gt;
| unsigned, Function&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| unsigned, Buffer mask&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| signed, Reference value&lt;br /&gt;
|-&lt;br /&gt;
| 24-31&lt;br /&gt;
| unsigned, Mask&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure stencil testing.&lt;br /&gt;
&lt;br /&gt;
Function values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Never&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Always&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Equal&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Not equal&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Less than&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Less than or equal&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Greater than&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Greater than or equal&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_STENCIL_OP ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-2&lt;br /&gt;
| unsigned, Fail operation&lt;br /&gt;
|-&lt;br /&gt;
| 4-6&lt;br /&gt;
| unsigned, Z-fail operation&lt;br /&gt;
|-&lt;br /&gt;
| 8-10&lt;br /&gt;
| unsigned, Z-pass operation&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure stencil result operations.&lt;br /&gt;
&lt;br /&gt;
Operation values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Keep&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Zero&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Replace&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Increment&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Decrement&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Invert&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Increment and wrap&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Decrement and wrap&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_DEPTH_COLOR_MASK ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Depth test enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 4-6&lt;br /&gt;
| unsigned, Depth function&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| unsigned, Red write enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| unsigned, Green write enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| unsigned, Blue write enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| unsigned, Alpha write enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 12&lt;br /&gt;
| unsigned, Depth write enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to depth testing and framebuffer write masking.&lt;br /&gt;
&lt;br /&gt;
Note that setting the &amp;quot;Depth test enabled&amp;quot; bit to 0 will &#039;&#039;not&#039;&#039; also disable depth writes. It will instead behave as if the depth function were set to &amp;quot;Always&amp;quot;. To completely disable depth-related operations both the depth test and depth write bits must be disabled.&lt;br /&gt;
&lt;br /&gt;
Depth function values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Never&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Always&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Equal&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Not equal&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Less than&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Less than or equal&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Greater than&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Greater than or equal&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_FRAMEBUFFER_INVALIDATE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Trigger (0 = idle, 1 = invalidate)&lt;br /&gt;
|-&lt;br /&gt;
| 1-31&lt;br /&gt;
| 0x0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Writing 1 to this register invalidates the framebuffer cache. This should be done when changing the framebuffer or when it is cleared before rendering. Note that it does &#039;&#039;&#039;not&#039;&#039;&#039; flush the cache, so it should always be preceded by a write to GPUREG_FRAMEBUFFER_FLUSH.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_FRAMEBUFFER_FLUSH ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Trigger (0 = idle, 1 = flush)&lt;br /&gt;
|-&lt;br /&gt;
| 1-31&lt;br /&gt;
| 0x0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Writing 1 to this register flushes the framebuffer cache to memory. This should be done after rendering before changing the framebuffer or using rendering results.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_COLORBUFFER_READ ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, Allow read (0 = disable, 0xF = enable)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures read access from the color buffer.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_COLORBUFFER_WRITE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, Allow write (0 = disable, 0xF = enable)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures write access to the color buffer.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_DEPTHBUFFER_READ ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Allow stencil read (0 = disable, 1 = enable)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| unsigned, Allow depth read (0 = disable, 1 = enable)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures read access from the depth and stencil buffers.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_DEPTHBUFFER_WRITE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Allow stencil write (0 = disable, 1 = enable)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| unsigned, Allow depth write (0 = disable, 1 = enable)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures write access to the depth and stencil buffers.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_DEPTHBUFFER_FORMAT ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-1&lt;br /&gt;
| unsigned, Format&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the depth buffer data format.&lt;br /&gt;
&lt;br /&gt;
Format values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| 16-bit depth&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 24-bit depth&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 24-bit depth + 8-bit stencil&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_COLORBUFFER_FORMAT ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-1&lt;br /&gt;
| unsigned, Pixel size&lt;br /&gt;
|-&lt;br /&gt;
| 16-18&lt;br /&gt;
| unsigned, Format&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the color buffer data format. Color components are laid out in reverse byte order in memory, with the most significant bits used first.&lt;br /&gt;
&lt;br /&gt;
Pixel size values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| 16-bit color&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 32-bit color&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Format values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| RGBA8/Gas&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| RGB5A1&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| RGB565&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| RGBA4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_EARLYDEPTH_TEST2 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register enables the early depth test.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_FRAMEBUFFER_BLOCK32 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Render block mode&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the framebuffer block mode. To untile the color buffer when using the 32x32 block format, use bit 16 of the [[GPU/External_Registers#Transfer_Engine|display transfer flags]]. It is unknown if there are any advantages to using the 32x32 format.&lt;br /&gt;
&lt;br /&gt;
Render block mode values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| 8x8 blocks&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 32x32 blocks&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_DEPTHBUFFER_LOC ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-27&lt;br /&gt;
| unsigned, Depth buffer physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the depth buffer physical address.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_COLORBUFFER_LOC ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-27&lt;br /&gt;
| unsigned, Color buffer physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the color buffer physical address.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_FRAMEBUFFER_DIM ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-10&lt;br /&gt;
| unsigned, Width&lt;br /&gt;
|-&lt;br /&gt;
| 12-21&lt;br /&gt;
| unsigned, Height - 1&lt;br /&gt;
|-&lt;br /&gt;
| 24&lt;br /&gt;
| unsigned, Vertical flip? (0 = enable, 1 = disable)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the framebuffer dimensions.&lt;br /&gt;
&lt;br /&gt;
===	GPUREG_GAS_LIGHT_XY ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Planar shading minimum intensity&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| unsigned, Planar shading maximum intensity&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| unsigned, Planar shading density attenuation&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures gas light planar shading.&lt;br /&gt;
&lt;br /&gt;
===	GPUREG_GAS_LIGHT_Z ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, View shading minimum intensity&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| unsigned, View shading maximum intensity&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| unsigned, View shading density attenuation&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures gas light view shading.&lt;br /&gt;
&lt;br /&gt;
===	GPUREG_GAS_LIGHT_Z_COLOR ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, View shading effect in line-of-sight direction&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| Gas color LUT input&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures gas light shading in the line-of-sight direction, and the input to the gas color LUT.&lt;br /&gt;
&lt;br /&gt;
Color LUT input values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Gas density&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Light factor&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_GAS_LUT_INDEX ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-15&lt;br /&gt;
| unsigned, Index&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to set what index to write to with GPUREG_GAS_LUT_DATA&#039;&#039;i&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_GAS_LUT_DATA ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| LUT data&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers behave as a FIFO queue. Each write to these registers writes the provided value to the gas look-up table, starting at the index selected with GPUREG_GAS_LUT_INDEX.&lt;br /&gt;
&lt;br /&gt;
==== Gas Look-Up Table ====&lt;br /&gt;
&lt;br /&gt;
16 elements:&lt;br /&gt;
&lt;br /&gt;
First 8 elements:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| signed, Red&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| signed, Green&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| signed, Blue&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Last 8 elements:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Red&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| unsigned, Green&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| unsigned, Blue&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===	GPUREG_GAS_DELTAZ_DEPTH ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-23&lt;br /&gt;
| fixed0.16.8, Depth direction attenuation proportion&lt;br /&gt;
|-&lt;br /&gt;
| 24-25&lt;br /&gt;
| unsigned, Depth function&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure the gas depth direction attenuation proportion, as well as the gas depth function.&lt;br /&gt;
&lt;br /&gt;
Gas depth function values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Never&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Always&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Greater than/Greater than or equal&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Less than/Less than or equal/Equal/Not equal&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===	GPUREG_FRAGOP_SHADOW ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-15&lt;br /&gt;
| float1.5.10, Sum of penumbra scale and penumbra bias&lt;br /&gt;
|-&lt;br /&gt;
| 16-31&lt;br /&gt;
| float1.5.10, Penumbra scale with reversed sign&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure shadow properties.&lt;br /&gt;
&lt;br /&gt;
== Fragment lighting registers ==&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHT&#039;&#039;i&#039;&#039;_SPECULAR0 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Blue&lt;br /&gt;
|-&lt;br /&gt;
| 10-17&lt;br /&gt;
| unsigned, Green&lt;br /&gt;
|-&lt;br /&gt;
| 20-27&lt;br /&gt;
| unsigned, Red&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers contain the specular0 color of the corresponding light. Usually set to material_specular0*lightX_specular0.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHT&#039;&#039;i&#039;&#039;_SPECULAR1 ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Blue&lt;br /&gt;
|-&lt;br /&gt;
| 10-17&lt;br /&gt;
| unsigned, Green&lt;br /&gt;
|-&lt;br /&gt;
| 20-27&lt;br /&gt;
| unsigned, Red&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers contain the specular1 color of the corresponding light. Usually set to material_specular1*lightX_specular1.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHT&#039;&#039;i&#039;&#039;_DIFFUSE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Blue&lt;br /&gt;
|-&lt;br /&gt;
| 10-17&lt;br /&gt;
| unsigned, Green&lt;br /&gt;
|-&lt;br /&gt;
| 20-27&lt;br /&gt;
| unsigned, Red&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers contain the diffuse color of the corresponding light. Usually set to material_diffuse*lightX_diffuse.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHT&#039;&#039;i&#039;&#039;_AMBIENT ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Blue&lt;br /&gt;
|-&lt;br /&gt;
| 10-17&lt;br /&gt;
| unsigned, Green&lt;br /&gt;
|-&lt;br /&gt;
| 20-27&lt;br /&gt;
| unsigned, Red&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers contain the ambient color of the corresponding light. Usually set to material_ambient*lightX_ambient.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHT&#039;&#039;i&#039;&#039;_XY ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-15&lt;br /&gt;
| float1.5.10, X coordinate&lt;br /&gt;
|-&lt;br /&gt;
| 16-31&lt;br /&gt;
| float1.5.10, Y coordinate&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers (along with GPUREG_LIGHT&#039;&#039;i&#039;&#039;_Z) represent the light position (for a positional light) or the light direction vector (for a directional light) of the corresponding light.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHT&#039;&#039;i&#039;&#039;_Z ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-15&lt;br /&gt;
| float1.5.10, Z coordinate&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers (along with GPUREG_LIGHT&#039;&#039;i&#039;&#039;_XY) represent the light position (for a positional light) or the light direction vector (for a directional light) of the corresponding light.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHT&#039;&#039;i&#039;&#039;_SPOTDIR_XY ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-12&lt;br /&gt;
| fixed1.1.11, X coordinate (negated)&lt;br /&gt;
|-&lt;br /&gt;
| 16-28&lt;br /&gt;
| fixed1.1.11, Y coordinate (negated)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers (along with GPUREG_LIGHT&#039;&#039;i&#039;&#039;_SPOTDIR_Z) represent the spot direction (unitary) vector of the corresponding light.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHT&#039;&#039;i&#039;&#039;_SPOTDIR_Z ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-12&lt;br /&gt;
| fixed1.1.11, Z coordinate (negated)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers (along with GPUREG_LIGHT&#039;&#039;i&#039;&#039;_SPOTDIR_XY) represent the spot direction (unitary) vector of the corresponding light.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHT&#039;&#039;i&#039;&#039;_CONFIG ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Light type (0 = positional light, 1 = directional light)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| unsigned, Two side diffuse (0 = one side, 1 = both sides)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| unsigned, Use geometric factor 0 (0 = don&#039;t use, 1 = use)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| unsigned, Use geometric factor 1 (0 = don&#039;t use, 1 = use)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures a light&#039;s properties.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHT&#039;&#039;i&#039;&#039;_ATTENUATION_BIAS ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-19&lt;br /&gt;
| float1.7.12, Distance attenuation bias&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers contain the distance attenuation bias value of the corresponding light. The attenuation factor is DA(clip(bias + scale*distance, 0.0, 1.0)).&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHT&#039;&#039;i&#039;&#039;_ATTENUATION_SCALE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-19&lt;br /&gt;
| float1.7.12, Distance attenuation scale&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers contain the distance attenuation scale value of the corresponding light. The attenuation factor is DA(clip(bias + scale*distance, 0.0, 1.0)).&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHTING_AMBIENT ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Blue&lt;br /&gt;
|-&lt;br /&gt;
| 10-17&lt;br /&gt;
| unsigned, Green&lt;br /&gt;
|-&lt;br /&gt;
| 20-27&lt;br /&gt;
| unsigned, Red&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register contains the initial value of the fragment primary color before the partial colors that correspond to each enabled light are added. Usually set to material_emission + material_ambient*scene_ambient.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHTING_NUM_LIGHTS ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-2&lt;br /&gt;
| unsigned, Number of active lights - 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the number of active lights.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHTING_CONFIG0 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Shadow factor enabled (0 = disabled, 1 = enabled) (usually accompanied by bit 16, 17, or 18)&lt;br /&gt;
|-&lt;br /&gt;
| 2-3&lt;br /&gt;
| unsigned, Fresnel selector&lt;br /&gt;
|-&lt;br /&gt;
| 4-7&lt;br /&gt;
| unsigned, Light environment configuration&lt;br /&gt;
|-&lt;br /&gt;
| 8-11&lt;br /&gt;
| 0x4&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| unsigned, Apply shadow attenuation to primary color (0 = don&#039;t apply, 1 = apply)&lt;br /&gt;
|-&lt;br /&gt;
| 17&lt;br /&gt;
| unsigned, Apply shadow attenuation to secondary color (0 = don&#039;t apply, 1 = apply)&lt;br /&gt;
|-&lt;br /&gt;
| 18&lt;br /&gt;
| unsigned, Invert shadow attenuation (0 = don&#039;t invert, 1 = invert)&lt;br /&gt;
|-&lt;br /&gt;
| 19&lt;br /&gt;
| unsigned, Apply shadow attenuation to alpha component (0 = don&#039;t apply, 1 = apply)&lt;br /&gt;
|-&lt;br /&gt;
| 22-23&lt;br /&gt;
| unsigned, Bump map texture unit&lt;br /&gt;
|-&lt;br /&gt;
| 24-25&lt;br /&gt;
| unsigned, Shadow map texture unit&lt;br /&gt;
|-&lt;br /&gt;
| 27&lt;br /&gt;
| unsigned, Clamp highlights (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 28-29&lt;br /&gt;
| unsigned, Bump mode&lt;br /&gt;
|-&lt;br /&gt;
| 30&lt;br /&gt;
| unsigned, Recalculate bump vectors (0 = enabled, 1 = disabled) (usually set to 1 when bump mode is not 0)&lt;br /&gt;
|-&lt;br /&gt;
| 31&lt;br /&gt;
| 0x1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the light environment.&lt;br /&gt;
&lt;br /&gt;
Fresnel selector values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| None&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Primary alpha&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Secondary alpha&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Primary and secondary alpha&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The light environment configuration controls which LUTs are available for use. If a LUT is not available in the selected configuration, its value will always read a constant 1.0 regardless of the enable state in GPUREG_LIGHTING_CONFIG1. If RR is enabled but not RG or RB, the output of RR is used for the three components; Red, Green and Blue.&lt;br /&gt;
&lt;br /&gt;
Light environment configuration values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
! Available LUTs&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Configuration 0&lt;br /&gt;
| D0, RR, SP, DA&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Configuration 1&lt;br /&gt;
| FR, RR, SP, DA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Configuration 2&lt;br /&gt;
| D0, D1, RR, DA&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Configuration 3&lt;br /&gt;
| D0, D1, FR, DA&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Configuration 4&lt;br /&gt;
| All except for FR&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Configuration 5&lt;br /&gt;
| All except for D1&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Configuration 6&lt;br /&gt;
| All except for RB and RG&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| Configuration 7&lt;br /&gt;
| All&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Bump mode values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Not used&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Use as bump map&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Use as tangent map&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHTING_CONFIG1 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Fragment light source 0 shadows disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| unsigned, Fragment light source 1 shadows disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| unsigned, Fragment light source 2 shadows disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| unsigned, Fragment light source 3 shadows disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| unsigned, Fragment light source 4 shadows disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| unsigned, Fragment light source 5 shadows disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| unsigned, Fragment light source 6 shadows disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| unsigned, Fragment light source 7 shadows disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| unsigned, Fragment light source 0 spot light disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| unsigned, Fragment light source 1 spot light disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| unsigned, Fragment light source 2 spot light disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| unsigned, Fragment light source 3 spot light disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 12&lt;br /&gt;
| unsigned, Fragment light source 4 spot light disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 13&lt;br /&gt;
| unsigned, Fragment light source 5 spot light disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 14&lt;br /&gt;
| unsigned, Fragment light source 6 spot light disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| unsigned, Fragment light source 7 spot light disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| unsigned, Term 0 distribution component D0 LUT disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 17&lt;br /&gt;
| unsigned, Term 1 distribution component D1 LUT disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 18&lt;br /&gt;
| 0x1&lt;br /&gt;
|-&lt;br /&gt;
| 19&lt;br /&gt;
| unsigned, Fresnel FR LUT disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 20&lt;br /&gt;
| unsigned, Term 1 reflection component RB LUT disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 21&lt;br /&gt;
| unsigned, Term 1 reflection component RG LUT disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 22&lt;br /&gt;
| unsigned, Term 1 reflection component RR LUT disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 24&lt;br /&gt;
| unsigned, Fragment light source 0 distance attenuation disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 25&lt;br /&gt;
| unsigned, Fragment light source 1 distance attenuation disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 26&lt;br /&gt;
| unsigned, Fragment light source 2 distance attenuation disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 27&lt;br /&gt;
| unsigned, Fragment light source 3 distance attenuation disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 28&lt;br /&gt;
| unsigned, Fragment light source 4 distance attenuation disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 29&lt;br /&gt;
| unsigned, Fragment light source 5 distance attenuation disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 30&lt;br /&gt;
| unsigned, Fragment light source 6 distance attenuation disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 31&lt;br /&gt;
| unsigned, Fragment light source 7 distance attenuation disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to disable various aspects of the light environment.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHTING_LUT_INDEX ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Starting index&lt;br /&gt;
|-&lt;br /&gt;
| 8-12&lt;br /&gt;
| unsigned, Look-up table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register controls which LUT and what offset into it the GPUREG_LIGHTING_LUT_DATA&#039;&#039;i&#039;&#039; register writes to.&lt;br /&gt;
&lt;br /&gt;
Lookup table values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| D0&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| D1&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| FR&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| RB&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| RG&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| RR&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| SP0-7&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| DA0-7&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHTING_ENABLE1 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Disabled (0 = enabled, 1 = disabled)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is set to 1 when fragment lighting is disabled, and to 0 when it is enabled.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHTING_LUT_DATA&#039;&#039;i&#039;&#039; ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-23&lt;br /&gt;
| LUT data&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Lighting LUT data is written here.&lt;br /&gt;
&lt;br /&gt;
A LUT contains data for the input domain [-1.0, 1.0], which is indexed using a signed 8-bit number [-128, 127]. Therefore a LUT contains 256 entries. The index of a value is (int)(x/127.0f) &amp;amp; 0xFF.&lt;br /&gt;
&lt;br /&gt;
DA: The input domain is [0.0, 1.0], and the index is an unsigned 8-bit number [0, 255] instead.&lt;br /&gt;
&lt;br /&gt;
Format of an entry:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-11&lt;br /&gt;
| fixed0.0.12, Entry value&lt;br /&gt;
|-&lt;br /&gt;
| 12-23&lt;br /&gt;
| fixed1.0.11, Absolute value of the difference between the next entry and this entry, used to implement linear interpolation&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHTING_LUTINPUT_ABS ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| unsigned, abs() flag for the input of D0 (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| unsigned, abs() flag for the input of D1 (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| unsigned, abs() flag for the input of SP (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 13&lt;br /&gt;
| unsigned, abs() flag for the input of FR (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 17&lt;br /&gt;
| unsigned, abs() flag for the input of RB (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 21&lt;br /&gt;
| unsigned, abs() flag for the input of RG (0 = enabled, 1 = disabled)&lt;br /&gt;
|-&lt;br /&gt;
| 25&lt;br /&gt;
| unsigned, abs() flag for the input of RR (0 = enabled, 1 = disabled)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register controls whether the absolute value of the input is taken before using a LUT.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHTING_LUTINPUT_SELECT ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-2&lt;br /&gt;
| unsigned, Input selector for D0&lt;br /&gt;
|-&lt;br /&gt;
| 4-6&lt;br /&gt;
| unsigned, Input selector for D1&lt;br /&gt;
|-&lt;br /&gt;
| 8-10&lt;br /&gt;
| unsigned, Input selector for SP&lt;br /&gt;
|-&lt;br /&gt;
| 12-14&lt;br /&gt;
| unsigned, Input selector for FR&lt;br /&gt;
|-&lt;br /&gt;
| 16-18&lt;br /&gt;
| unsigned, Input selector for RB&lt;br /&gt;
|-&lt;br /&gt;
| 20-22&lt;br /&gt;
| unsigned, Input selector for RG&lt;br /&gt;
|-&lt;br /&gt;
| 24-26&lt;br /&gt;
| unsigned, Input selector for RR&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register selects the input from LUTs.&lt;br /&gt;
&lt;br /&gt;
Input selector values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| N·H&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| V·H&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| N·V&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| L·N&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| -L·P (aka Spotlight aka SP)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| cos φ (aka CP)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHTING_LUTINPUT_SCALE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-2&lt;br /&gt;
| unsigned, Scaler selector for D0&lt;br /&gt;
|-&lt;br /&gt;
| 4-6&lt;br /&gt;
| unsigned, Scaler selector for D1&lt;br /&gt;
|-&lt;br /&gt;
| 8-10&lt;br /&gt;
| unsigned, Scaler selector for SP&lt;br /&gt;
|-&lt;br /&gt;
| 12-14&lt;br /&gt;
| unsigned, Scaler selector for FR&lt;br /&gt;
|-&lt;br /&gt;
| 16-18&lt;br /&gt;
| unsigned, Scaler selector for RB&lt;br /&gt;
|-&lt;br /&gt;
| 20-22&lt;br /&gt;
| unsigned, Scaler selector for RG&lt;br /&gt;
|-&lt;br /&gt;
| 24-26&lt;br /&gt;
| unsigned, Scaler selector for RR&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register controls the scaling that is applied to the output of a LUT.&lt;br /&gt;
&lt;br /&gt;
Scaler selector values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| 1x&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 2x&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 4x&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8x&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.25x&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.5x&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_LIGHTING_LIGHT_PERMUTATION ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-2&lt;br /&gt;
| unsigned, ID of the 1st enabled light&lt;br /&gt;
|-&lt;br /&gt;
| 4-6&lt;br /&gt;
| unsigned, ID of the 2nd enabled light&lt;br /&gt;
|-&lt;br /&gt;
| 8-10&lt;br /&gt;
| unsigned, ID of the 3rd enabled light&lt;br /&gt;
|-&lt;br /&gt;
| 12-14&lt;br /&gt;
| unsigned, ID of the 4th enabled light&lt;br /&gt;
|-&lt;br /&gt;
| 16-18&lt;br /&gt;
| unsigned, ID of the 5th enabled light&lt;br /&gt;
|-&lt;br /&gt;
| 20-22&lt;br /&gt;
| unsigned, ID of the 6th enabled light&lt;br /&gt;
|-&lt;br /&gt;
| 24-26&lt;br /&gt;
| unsigned, ID of the 7th enabled light&lt;br /&gt;
|-&lt;br /&gt;
| 28-30&lt;br /&gt;
| unsigned, ID of the 8th enabled light&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets the IDs of enabled light sources.&lt;br /&gt;
&lt;br /&gt;
== Geometry pipeline registers ==&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_ATTRIBBUFFERS_LOC ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 1-28&lt;br /&gt;
| unsigned, Vertex arrays base address&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets the base address of all vertex arrays.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_ATTRIBBUFFERS_FORMAT_LOW ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-1&lt;br /&gt;
| unsigned, Vertex attribute 0 type&lt;br /&gt;
|-&lt;br /&gt;
| 2-3&lt;br /&gt;
| unsigned, Vertex attribute 0 size&lt;br /&gt;
|-&lt;br /&gt;
| 4-5&lt;br /&gt;
| unsigned, Vertex attribute 1 type&lt;br /&gt;
|-&lt;br /&gt;
| 6-7&lt;br /&gt;
| unsigned, Vertex attribute 1 size&lt;br /&gt;
|-&lt;br /&gt;
| 8-9&lt;br /&gt;
| unsigned, Vertex attribute 2 type&lt;br /&gt;
|-&lt;br /&gt;
| 10-11&lt;br /&gt;
| unsigned, Vertex attribute 2 size&lt;br /&gt;
|-&lt;br /&gt;
| 12-13&lt;br /&gt;
| unsigned, Vertex attribute 3 type&lt;br /&gt;
|-&lt;br /&gt;
| 14-15&lt;br /&gt;
| unsigned, Vertex attribute 3 size&lt;br /&gt;
|-&lt;br /&gt;
| 16-17&lt;br /&gt;
| unsigned, Vertex attribute 4 type&lt;br /&gt;
|-&lt;br /&gt;
| 18-19&lt;br /&gt;
| unsigned, Vertex attribute 4 size&lt;br /&gt;
|-&lt;br /&gt;
| 20-21&lt;br /&gt;
| unsigned, Vertex attribute 5 type&lt;br /&gt;
|-&lt;br /&gt;
| 22-23&lt;br /&gt;
| unsigned, Vertex attribute 5 size&lt;br /&gt;
|-&lt;br /&gt;
| 24-25&lt;br /&gt;
| unsigned, Vertex attribute 6 type&lt;br /&gt;
|-&lt;br /&gt;
| 26-27&lt;br /&gt;
| unsigned, Vertex attribute 6 size&lt;br /&gt;
|-&lt;br /&gt;
| 28-29&lt;br /&gt;
| unsigned, Vertex attribute 7 type&lt;br /&gt;
|-&lt;br /&gt;
| 30-31&lt;br /&gt;
| unsigned, Vertex attribute 7 size&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the types and sizes of the first 8 vertex attributes.&lt;br /&gt;
&lt;br /&gt;
Vertex attribute type values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Byte&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Unsigned byte&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Short&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Float&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Vertex attribute size values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| 8 bits&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 16 bits&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 24 bits&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 32 bits&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_ATTRIBBUFFERS_FORMAT_HIGH ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-1&lt;br /&gt;
| unsigned, Vertex attribute 8 type&lt;br /&gt;
|-&lt;br /&gt;
| 2-3&lt;br /&gt;
| unsigned, Vertex attribute 8 size&lt;br /&gt;
|-&lt;br /&gt;
| 4-5&lt;br /&gt;
| unsigned, Vertex attribute 9 type&lt;br /&gt;
|-&lt;br /&gt;
| 6-7&lt;br /&gt;
| unsigned, Vertex attribute 9 size&lt;br /&gt;
|-&lt;br /&gt;
| 8-9&lt;br /&gt;
| unsigned, Vertex attribute 10 type&lt;br /&gt;
|-&lt;br /&gt;
| 10-11&lt;br /&gt;
| unsigned, Vertex attribute 10 size&lt;br /&gt;
|-&lt;br /&gt;
| 12-13&lt;br /&gt;
| unsigned, Vertex attribute 11 type&lt;br /&gt;
|-&lt;br /&gt;
| 14-15&lt;br /&gt;
| unsigned, Vertex attribute 11 size&lt;br /&gt;
|-&lt;br /&gt;
| 16-27&lt;br /&gt;
| unsigned, Fixed vertex attribute mask&lt;br /&gt;
|-&lt;br /&gt;
| 28-31&lt;br /&gt;
| unsigned, Total vertex attribute count - 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the types and sizes of the last 4 vertex attributes, along with the attribute mask and the total attribute count.&lt;br /&gt;
&lt;br /&gt;
See GPUREG_ATTRIBBUFFERS_FORMAT_LOW for vertex attribute type and size values.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_ATTRIBBUFFER&#039;&#039;i&#039;&#039;_OFFSET ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-27&lt;br /&gt;
| unsigned, Offset from base vertex arrays address&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the offset of a vertex array from the base vertex arrays address.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_ATTRIBBUFFER&#039;&#039;i&#039;&#039;_CONFIG1 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, Component 1&lt;br /&gt;
|-&lt;br /&gt;
| 4-7&lt;br /&gt;
| unsigned, Component 2&lt;br /&gt;
|-&lt;br /&gt;
| 8-11&lt;br /&gt;
| unsigned, Component 3&lt;br /&gt;
|-&lt;br /&gt;
| 12-15&lt;br /&gt;
| unsigned, Component 4&lt;br /&gt;
|-&lt;br /&gt;
| 16-19&lt;br /&gt;
| unsigned, Component 5&lt;br /&gt;
|-&lt;br /&gt;
| 20-23&lt;br /&gt;
| unsigned, Component 6&lt;br /&gt;
|-&lt;br /&gt;
| 24-27&lt;br /&gt;
| unsigned, Component 7&lt;br /&gt;
|-&lt;br /&gt;
| 28-31&lt;br /&gt;
| unsigned, Component 8&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the first 8 component types of a vertex array.&lt;br /&gt;
&lt;br /&gt;
Component values:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Vertex attribute 0&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Vertex attribute 1&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Vertex attribute 2&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Vertex attribute 3&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Vertex attribute 4&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Vertex attribute 5&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Vertex attribute 6&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Vertex attribute 7&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| Vertex attribute 8&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| Vertex attribute 9&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| Vertex attribute 10&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| Vertex attribute 11&lt;br /&gt;
|-&lt;br /&gt;
| 12&lt;br /&gt;
| 4-byte padding&lt;br /&gt;
|-&lt;br /&gt;
| 13&lt;br /&gt;
| 8-byte padding&lt;br /&gt;
|-&lt;br /&gt;
| 14&lt;br /&gt;
| 12-byte padding&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| 16-byte padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_ATTRIBBUFFER&#039;&#039;i&#039;&#039;_CONFIG2 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, Component 9&lt;br /&gt;
|-&lt;br /&gt;
| 4-7&lt;br /&gt;
| unsigned, Component 10&lt;br /&gt;
|-&lt;br /&gt;
| 8-11&lt;br /&gt;
| unsigned, Component 11&lt;br /&gt;
|-&lt;br /&gt;
| 12-15&lt;br /&gt;
| unsigned, Component 12&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| unsigned, Bytes per vertex&lt;br /&gt;
|-&lt;br /&gt;
| 28-31&lt;br /&gt;
| unsigned, Total number of components&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the last 4 component types of a vertex array, along with the bytes per vertex and the total number of components.&lt;br /&gt;
&lt;br /&gt;
See GPUREG_ATTRIBBUFFER&#039;&#039;i&#039;&#039;_CONFIG1 for component values.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_INDEXBUFFER_CONFIG ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-27&lt;br /&gt;
| unsigned, Offset from base vertex arrays address&lt;br /&gt;
|-&lt;br /&gt;
| 31&lt;br /&gt;
| Index type (0 = unsigned byte, 1 = unsigned short or drawing arrays)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the index array used when drawing elements.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_NUMVERTICES ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| unsigned, Number of vertices to render&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets the number of vertices to render.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_GEOSTAGE_CONFIG ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-1&lt;br /&gt;
| unsigned, Geometry shader in use (0 = not in use, 2 = in use)&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| unsigned, Drawing triangle elements (0 = not, 1 = drawing triangle elements)&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0x0&lt;br /&gt;
|-&lt;br /&gt;
| 31&lt;br /&gt;
| unsigned, Use reserved geometry shader subdivision (0 = don&#039;t use, 1 = use)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the geometry stage of the GPU pipeline.&lt;br /&gt;
&lt;br /&gt;
When using vertex buffers and drawing elements in triangles mode, bit 8 is set to 1, else it is set to 0.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_VERTEX_OFFSET ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| unsigned, Starting vertex offset&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets the offset of the first vertex in an array to render.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_POST_VERTEX_CACHE_NUM ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Number of entries in the post-vertex cache (usually 0x4 or 0x84)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the post-vertex cache.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_DRAWARRAYS ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| unsigned, Trigger (0 = idle, non-zero = draw arrays)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register triggers drawing vertex arrays.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_DRAWELEMENTS ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| unsigned, Trigger (0 = idle, non-zero = draw elements)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register triggers drawing vertex array elements.&lt;br /&gt;
&lt;br /&gt;
=== 	GPUREG_VTX_FUNC ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| unsigned, Trigger (0 = idle, non-zero = clear post-vertex cache)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register triggers clearing the post-vertex cache.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_FIXEDATTRIB_INDEX ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, Fixed attribute index (0-11, 0xF = immediate-mode submission)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register selects the index of the fixed attribute to be input with GPUREG_FIXEDATTRIB_DATA&#039;&#039;i&#039;&#039;. See [[GPU/Fixed Vertex Attributes]] and [[GPU/Immediate-Mode Vertex Submission]] for usage info.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_FIXEDATTRIB_DATA&#039;&#039;i&#039;&#039; ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | &#039;&#039;&#039;DATA0:&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| float1.7.16, Vertex attribute element 3 (Z) (bits 16-23)&lt;br /&gt;
|-&lt;br /&gt;
| 8-31&lt;br /&gt;
| float1.7.16, Vertex attribute element 4 (W)&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | &#039;&#039;&#039;DATA1:&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| 0-15&lt;br /&gt;
| float1.7.16, Vertex attribute element 2 (Y) (bits 8-23)&lt;br /&gt;
|-&lt;br /&gt;
| 16-31&lt;br /&gt;
| float1.7.16, Vertex attribute element 3 (Z) (bits 0-15)&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; | &#039;&#039;&#039;DATA2:&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| 0-23&lt;br /&gt;
| float1.7.16, Vertex attribute element 1 (X)&lt;br /&gt;
|-&lt;br /&gt;
| 24-31&lt;br /&gt;
| float1.7.16, Vertex attribute element 2 (Y) (bits 0-7)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Accepts four 24-bit floating-point values that make up a vertex attribute. Stored in the fixed attribute currently specified with GPUREG_FIXEDATTRIB_INDEX. If immediate-mode vertex submission is enabled (by writing 0xF to the index register) then vertex data is input here directly.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_CMDBUF_SIZE0 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-20&lt;br /&gt;
| unsigned, Size of command buffer 0 &amp;gt;&amp;gt; 3&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets the size of the first command buffer.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_CMDBUF_SIZE1 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-20&lt;br /&gt;
| unsigned, Size of command buffer 1 &amp;gt;&amp;gt; 3&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets the size of the second command buffer.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_CMDBUF_ADDR0 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-28&lt;br /&gt;
| unsigned, Physical address of command buffer 0 &amp;gt;&amp;gt; 3&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets the physical address of the first command buffer.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_CMDBUF_ADDR1 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-28&lt;br /&gt;
| unsigned, Physical address of command buffer 1 &amp;gt;&amp;gt; 3&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets the physical address of the second command buffer.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_CMDBUF_JUMP0 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| unsigned, Trigger (0 = idle, non-zero = execute command buffer 0)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register triggers a jump to the first command buffer.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_CMDBUF_JUMP1 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| unsigned, Trigger (0 = idle, non-zero = execute command buffer 1)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register triggers a jump to the second command buffer.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_VSH_NUM_ATTR ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, Number of vertex shader input attributes - 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets the number of vertex shader input attributes.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_VSH_COM_MODE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Geometry shader configuration enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets whether to use the geometry shader configuration or reuse the vertex shader configuration for the geometry shader shading unit.&lt;br /&gt;
When disabled and the geometry unit is not in use, as configured by GPUREG_GEOSTAGE_CONFIG, uniforms, outmap mask, program code and swizzle data are propagated to the geometry shader unit. &lt;br /&gt;
&lt;br /&gt;
=== GPUREG_START_DRAW_FUNC0 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Mode (0 = drawing, 1 = configuration)&lt;br /&gt;
|-&lt;br /&gt;
| 1-7&lt;br /&gt;
| 0x0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is related to drawing. When the mode value is set to 1, rendering is not performed properly. When set to 0, changes to the vertex shader configuration registers are not applied correctly. Because of this, it is usually initialized to 1, set to 0 immediately before triggering a draw, and set back to 1 immediately after triggering a draw.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_VSH_OUTMAP_TOTAL1 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, Number of vertex shader output map registers - 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets the number of vertex shader output map registers.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_VSH_OUTMAP_TOTAL2 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, Number of vertex shader output map registers - 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets the number of vertex shader output map registers.&lt;br /&gt;
&lt;br /&gt;
===	GPUREG_GSH_MISC0 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| unsigned, Misc data (0x00000001 = Reserved geometry shader subdivision in use, 0x01004302 = Particle system in use, 0 otherwise)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures miscellaneous geometry shader properties.&lt;br /&gt;
&lt;br /&gt;
===	GPUREG_GEOSTAGE_CONFIG2 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Function indicator (with vertex buffers: 0 = draw elements, 1 = draw arrays, without: 0 = not inputting, 1 = inputting vertex attribute data)&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| unsigned, Drawing triangle elements (0 = not, 1 = drawing triangle elements)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the geometry stage of the GPU pipeline.&lt;br /&gt;
&lt;br /&gt;
When using vertex buffers, bit 0 is set to 1 before drawing arrays, and cleared to 0 immediately after. When using immediate mode to directly input vertex attribute data, bit 0 is set to 1 before inputting vertex attribute data, and cleared to 0 immediately after. While bit 0 is set to 1, some register writes outside of the 0x200-0x254 and 0x280-0x2DF ranges may be processed incorrectly.&lt;br /&gt;
&lt;br /&gt;
When using vertex buffers and drawing elements in triangles mode, bit 8 is set to 1, else it is set to 0.&lt;br /&gt;
&lt;br /&gt;
===	GPUREG_GSH_MISC1 ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-4&lt;br /&gt;
| unsigned, Reserved geometry shader subdivision type (2 = Loop, 3 = Catmull-Clark)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures the type of reserved geometry shader subdivision in use. The value is ignored when a subdivision is not in use.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_PRIMITIVE_CONFIG ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned,  Number of vertex shader output map registers - 1&lt;br /&gt;
|-&lt;br /&gt;
| 8-9&lt;br /&gt;
| unsigned, Primitive mode&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register configures primitive drawing.&lt;br /&gt;
&lt;br /&gt;
Primitive mode value:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Triangles&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Triangle strip&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Triangle fan&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Geometry primitive&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_RESTART_PRIMITIVE ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Trigger (0 = idle, 1 = reset primitive)&lt;br /&gt;
|-&lt;br /&gt;
| 1-31&lt;br /&gt;
| 0x0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register triggers resetting primitive drawing.&lt;br /&gt;
&lt;br /&gt;
== Shader registers ==&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_&#039;&#039;SH&#039;&#039;_BOOLUNIFORM ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Boolean register b0 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| unsigned, Boolean register b1 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| unsigned, Boolean register b2 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| unsigned, Boolean register b3 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| unsigned, Boolean register b4 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| unsigned, Boolean register b5 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| unsigned, Boolean register b6 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| unsigned, Boolean register b7 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| unsigned, Boolean register b8 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| unsigned, Boolean register b9 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| unsigned, Boolean register b10 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| unsigned, Boolean register b11 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 12&lt;br /&gt;
| unsigned, Boolean register b12 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 13&lt;br /&gt;
| unsigned, Boolean register b13 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 14&lt;br /&gt;
| unsigned, Boolean register b14 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| unsigned, Boolean register b15 value (0 = false, 1 = true)&lt;br /&gt;
|-&lt;br /&gt;
| 16-31&lt;br /&gt;
| 0x7FFF&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to set a shader unit&#039;s boolean registers.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_&#039;&#039;SH&#039;&#039;_INTUNIFORM_I&#039;&#039;i&#039;&#039; ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Integer register i&#039;&#039;i&#039;&#039; X value&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| unsigned, Integer register i&#039;&#039;i&#039;&#039; Y value&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| unsigned, Integer register i&#039;&#039;i&#039;&#039; Z value&lt;br /&gt;
|-&lt;br /&gt;
| 24-31&lt;br /&gt;
| unsigned, Integer register i&#039;&#039;i&#039;&#039; W value&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers are used to set a shader unit&#039;s integer registers.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_&#039;&#039;SH&#039;&#039;_INPUTBUFFER_CONFIG ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, Input vertex attributes - 1&lt;br /&gt;
|-&lt;br /&gt;
| 8-15&lt;br /&gt;
| unsigned, Use reserved geometry shader subdivision (0 = don&#039;t use, 1 = use) (always 0 for vertex shaders)&lt;br /&gt;
|-&lt;br /&gt;
| 16-23&lt;br /&gt;
| 0x0&lt;br /&gt;
|-&lt;br /&gt;
| 24-31&lt;br /&gt;
| unsigned, Use geometry shader (0x8 = use, 0xA0 = don&#039;t use) (always 0xA0 for vertex shaders)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to configure a shader unit&#039;s input buffer.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_&#039;&#039;SH&#039;&#039;_ENTRYPOINT ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-15&lt;br /&gt;
| unsigned, Code entry point offset, in 32-bit words&lt;br /&gt;
|-&lt;br /&gt;
| 16-31&lt;br /&gt;
| 0x7FFF&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets a shader unit&#039;s code entry point.&lt;br /&gt;
&lt;br /&gt;
For geometry shaders, this sets the entry point for the single shader unit which can be dedicated to running geometry shaders, regardless of the current geometry stage mode. This is means that while this register is normally used to set the geometry shader entry point, it can also be used to set this single shader unit to run from a different entry point than the other three, even when running a vertex shader.&lt;br /&gt;
&lt;br /&gt;
For vertex shaders, this sets the entry point for the shader units set to vertex shader mode. Depending on the current geometry stage mode this can include either all 4 shader units or just 3 of them. &lt;br /&gt;
&lt;br /&gt;
=== GPUREG_&#039;&#039;SH&#039;&#039;_ATTRIBUTES_PERMUTATION_LOW ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, Vertex attribute 0 input register index&lt;br /&gt;
|-&lt;br /&gt;
| 4-7&lt;br /&gt;
| unsigned, Vertex attribute 1 input register index&lt;br /&gt;
|-&lt;br /&gt;
| 8-11&lt;br /&gt;
| unsigned, Vertex attribute 2 input register index&lt;br /&gt;
|-&lt;br /&gt;
| 12-15&lt;br /&gt;
| unsigned, Vertex attribute 3 input register index&lt;br /&gt;
|-&lt;br /&gt;
| 16-19&lt;br /&gt;
| unsigned, Vertex attribute 4 input register index&lt;br /&gt;
|-&lt;br /&gt;
| 20-23&lt;br /&gt;
| unsigned, Vertex attribute 5 input register index&lt;br /&gt;
|-&lt;br /&gt;
| 24-27&lt;br /&gt;
| unsigned, Vertex attribute 6 input register index&lt;br /&gt;
|-&lt;br /&gt;
| 28-31&lt;br /&gt;
| unsigned, Vertex attribute 7 input register index&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets the shader unit input register index which will correspond to each attribute contained by the input buffer (which in the case of geometry shaders is the vertex shader output buffer) for the first 8 attributes. For example, having bits 0-3 set to 5 means that, in the shader program, v5 will contain the input buffer&#039;s 1st attribute.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_&#039;&#039;SH&#039;&#039;_ATTRIBUTES_PERMUTATION_HIGH ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| unsigned, Vertex attribute 8 input register index&lt;br /&gt;
|-&lt;br /&gt;
| 4-7&lt;br /&gt;
| unsigned, Vertex attribute 9 input register index&lt;br /&gt;
|-&lt;br /&gt;
| 8-11&lt;br /&gt;
| unsigned, Vertex attribute 10 input register index&lt;br /&gt;
|-&lt;br /&gt;
| 12-15&lt;br /&gt;
| unsigned, Vertex attribute 11 input register index&lt;br /&gt;
|-&lt;br /&gt;
| 16-19&lt;br /&gt;
| unsigned, Vertex attribute 12 input register index&lt;br /&gt;
|-&lt;br /&gt;
| 20-23&lt;br /&gt;
| unsigned, Vertex attribute 13 input register index&lt;br /&gt;
|-&lt;br /&gt;
| 24-27&lt;br /&gt;
| unsigned, Vertex attribute 14 input register index&lt;br /&gt;
|-&lt;br /&gt;
| 28-31&lt;br /&gt;
| unsigned, Vertex attribute 15 input register index&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets the shader unit input register index which will correspond to each attribute contained by the input buffer (which in the case of geometry shaders is the vertex shader output buffer) for attributes 8 through 15. For example, having bits 0-3 set to 5 means that, in the shader program, v5 will contain the input buffer&#039;s 9th attribute.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_&#039;&#039;SH&#039;&#039;_OUTMAP_MASK ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| unsigned, Output register o0 enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| unsigned, Output register o1 enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| unsigned, Output register o2 enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| unsigned, Output register o3 enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| unsigned, Output register o4 enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| unsigned, Output register o5 enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| unsigned, Output register o6 enabled (0 = disabled, 1 = enabled)&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| unsigned, Output register o7 enabled (0 = disabled, 1 = enabled) (vertex shader only)&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| unsigned, Output register o8 enabled (0 = disabled, 1 = enabled) (vertex shader only)&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| unsigned, Output register o9 enabled (0 = disabled, 1 = enabled) (vertex shader only)&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| unsigned, Output register o10 enabled (0 = disabled, 1 = enabled) (vertex shader only)&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| unsigned, Output register o11 enabled (0 = disabled, 1 = enabled) (vertex shader only)&lt;br /&gt;
|-&lt;br /&gt;
| 12&lt;br /&gt;
| unsigned, Output register o12 enabled (0 = disabled, 1 = enabled) (vertex shader only)&lt;br /&gt;
|-&lt;br /&gt;
| 13&lt;br /&gt;
| unsigned, Output register o13 enabled (0 = disabled, 1 = enabled) (vertex shader only)&lt;br /&gt;
|-&lt;br /&gt;
| 14&lt;br /&gt;
| unsigned, Output register o14 enabled (0 = disabled, 1 = enabled) (vertex shader only)&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| unsigned, Output register o15 enabled (0 = disabled, 1 = enabled) (vertex shader only)&lt;br /&gt;
|-&lt;br /&gt;
| 16-31&lt;br /&gt;
| 0x0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register toggles a shader unit&#039;s output registers.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_&#039;&#039;SH&#039;&#039;_CODETRANSFER_END ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| unsigned, Signal transfer end (0 = idle, non-zero = signal)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register&#039;s value should be set to 1 in order to finalize the transfer of shader code.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_&#039;&#039;SH&#039;&#039;_FLOATUNIFORM_INDEX ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-7&lt;br /&gt;
| unsigned, Target floating-point register index (range 0-95, where 0 = c0 and 95 = c95)&lt;br /&gt;
|-&lt;br /&gt;
| 31&lt;br /&gt;
| unsigned, Transfer mode (0 = float1.7.16, 1 = float1.8.23)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register sets the shader unit&#039;s target floating-point register and transfer mode for the data transfer system. As such it is typically used right before [[#GPUREG_SH_FLOATUNIFORM_DATAi|GPUREG_&#039;&#039;SH&#039;&#039;_FLOATUNIFORM_DATA&#039;&#039;i&#039;&#039;]], though writing to one register does not make writing to the other mandatory.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_&#039;&#039;SH&#039;&#039;_FLOATUNIFORM_DATA&#039;&#039;i&#039;&#039; ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| Floating-point register component data&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to set the components of a shader unit&#039;s floating-point registers, each having 4 components. The data format which should be written to it depends on the transfer mode set with [[#GPUREG_SH_FLOATUNIFORM_INDEX|GPUREG_&#039;&#039;SH&#039;&#039;_FLOATUNIFORM_INDEX]]. This register functions as a FIFO queue: after each time a 4-component uniform register is successfully set, the target register index is incremented, meaning that groups of uniforms with contiguous register IDs can be set with only one initial write to [[#GPUREG_SH_FLOATUNIFORM_INDEX|GPUREG_&#039;&#039;SH&#039;&#039;_FLOATUNIFORM_INDEX]].&lt;br /&gt;
&lt;br /&gt;
* In the case of float24 transfer mode, data should be sent by writing three words which are the concatenation of the float24 value of the uniform register&#039;s 4 components, in the reverse order. Assuming each letter corresponds to 4 bits, the format becomes:&lt;br /&gt;
** first word : ZZWWWWWW&lt;br /&gt;
** second word : YYYYZZZZ&lt;br /&gt;
** third word : XXXXXXYY&lt;br /&gt;
* In the case of float32 transfer mode, data should be sent by writing four words which are each the float32 value of the uniform register&#039;s 4 components, in the reverse order.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_&#039;&#039;SH&#039;&#039;_CODETRANSFER_INDEX ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-11&lt;br /&gt;
| unsigned, Target shader code offset&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to set the offset at which upcoming shader code data transferred through [[#GPUREG_SH_CODETRANSFER_DATAi|GPUREG_&#039;&#039;SH&#039;&#039;_CODETRANSFER_DATA&#039;&#039;i&#039;&#039;]] should be written.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_&#039;&#039;SH&#039;&#039;_CODETRANSFER_DATA&#039;&#039;i&#039;&#039; ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| unsigned, Shader instruction data&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to transfer shader code data. This register behaves as a FIFO queue: each write to this register writes the provided value to the GPU shader code memory bank at the offset initially set by [[#GPUREG_SH_CODETRANSFER_INDEX|GPUREG_&#039;&#039;SH&#039;&#039;_CODETRANSFER_INDEX]]. The offset in question is incremented after each write to this register.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_&#039;&#039;SH&#039;&#039;_OPDESCS_INDEX ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-11&lt;br /&gt;
| unsigned, Target shader operand descriptor offset&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to set the offset at which upcoming shader operand descriptor data transferred through [[#GPUREG_SH_OPDESCS_DATAi|GPUREG_&#039;&#039;SH&#039;&#039;_OPDESCS_DATA&#039;&#039;i&#039;&#039;]] should be written.&lt;br /&gt;
&lt;br /&gt;
=== GPUREG_&#039;&#039;SH&#039;&#039;_OPDESCS_DATA&#039;&#039;i&#039;&#039; ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-31&lt;br /&gt;
| unsigned, Shader operand descriptor data&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This register is used to transfer shader operand descriptor data. This register behaves as a FIFO queue: each write to this register writes the provided value to the GPU shader operand descriptor memory bank at the offset initially set by [[#GPUREG_SH_OPDESCS_INDEX|GPUREG_&#039;&#039;SH&#039;&#039;_OPDESCS_INDEX]]. The offset in question is incremented after each write to this register.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=Flash_Filesystem&amp;diff=23624</id>
		<title>Flash Filesystem</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=Flash_Filesystem&amp;diff=23624"/>
		<updated>2025-07-27T06:32:34Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Nintendo 3DS has several differently sized NAND flash chips. Due to the NCSD header, the actual used size of the Old3DS NAND is 0x3AF00000-bytes(943MiB). On New3DS, the actual NAND size and the total size used by the partitions, is 0x4D800000-bytes(1240MiB).&lt;br /&gt;
&lt;br /&gt;
===Physical Size===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Device&lt;br /&gt;
! Manufacturer&lt;br /&gt;
! Size&lt;br /&gt;
|-&lt;br /&gt;
| 2DS&lt;br /&gt;
| Toshiba&lt;br /&gt;
| 0x3AF00000&lt;br /&gt;
|-&lt;br /&gt;
| 2DS&lt;br /&gt;
| Toshiba&lt;br /&gt;
| 0x76000000&lt;br /&gt;
|-&lt;br /&gt;
| 2DS&lt;br /&gt;
| Samsung&lt;br /&gt;
| 0x3BA00000&lt;br /&gt;
|-&lt;br /&gt;
| 2DS&lt;br /&gt;
| Samsung&lt;br /&gt;
| 0x4D800000&lt;br /&gt;
|-&lt;br /&gt;
| Old3DS&lt;br /&gt;
| Toshiba&lt;br /&gt;
| 0x3AF00000&lt;br /&gt;
|-&lt;br /&gt;
| Old3DS&lt;br /&gt;
| Samsung&lt;br /&gt;
| 0x3BA00000&lt;br /&gt;
|-&lt;br /&gt;
| New3DS&lt;br /&gt;
| Toshiba&lt;br /&gt;
| 0x76000000&lt;br /&gt;
|-&lt;br /&gt;
| New3DS&lt;br /&gt;
| Samsung&lt;br /&gt;
| 0x4D800000&lt;br /&gt;
|-&lt;br /&gt;
| New3DS&lt;br /&gt;
| Samsung&lt;br /&gt;
| 0x74800000&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Format===&lt;br /&gt;
Reading of the flash chip is possible through pinouts on the motherboard and has been performed successfully but the data is encrypted and can&#039;t be understood without first decrypting it.&lt;br /&gt;
&lt;br /&gt;
===Region Changing===&lt;br /&gt;
See [https://gist.github.com/yellows8/f15be7a51c38cea14f2c here].&lt;br /&gt;
&lt;br /&gt;
===Redirection to SD card===&lt;br /&gt;
See [[NAND_Redirection]].&lt;br /&gt;
&lt;br /&gt;
===Encryption===&lt;br /&gt;
&lt;br /&gt;
The NAND file system is encrypted using [[AES|AES-CTR]]. The TWL regions of NAND use the TWL NAND [[AES|keyslot]], while the nonce is the sha1 hash of the NAND CID, byte reversed. The CTR regions use the CTR NAND [[AES|keyslots]], while the nonce is the sha256 hash of the NAND CID. The keyslot used for each partition is determined by the NCSD partition FS type and encryption type. The TWL/CTR NAND regions are specified by the NCSD header. The first 0x0B100000 bytes of NAND is encrypted with the TWL keyslot, however before 0x00012E00 only the MBR partition table is encrypted with the TWL keyslot. That region contains the TWL partitions listed below.&lt;br /&gt;
&lt;br /&gt;
The New3DS CTRNAND partition uses a [[AES|keyslot]] separate from the Old3DS one.&lt;br /&gt;
&lt;br /&gt;
Note that re-encrypting a NAND image alone from another 3DS for use on a different 3DS is not enough to use that NAND image on a different 3DS: certain files in the &amp;quot;nand&amp;quot; partition would need modified/replaced as well.&lt;br /&gt;
&lt;br /&gt;
===NAND structure===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Old3DS&lt;br /&gt;
!  New3DS&lt;br /&gt;
!  Partition name&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  NCSD partition FS type&lt;br /&gt;
!  NCSD partition encryption type&lt;br /&gt;
!  NCSD partition index&lt;br /&gt;
!  [[AES_Registers|AES]] engine keyslot&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  &lt;br /&gt;
|  0x0&lt;br /&gt;
|  0x200&lt;br /&gt;
|  &lt;br /&gt;
|  &lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|  [[NCSD]] header, this contains the offsets/sizes of the below CTR-NAND partitions. This block also contains the TWL-NAND MBR partition table.&lt;br /&gt;
|-&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  &lt;br /&gt;
|  0x00000000&lt;br /&gt;
|  0x0B100000&lt;br /&gt;
|  0x01&lt;br /&gt;
|  0x01&lt;br /&gt;
|  0x00&lt;br /&gt;
|  0x03&lt;br /&gt;
|  TWL NAND region&lt;br /&gt;
|-&lt;br /&gt;
|  style=&amp;quot;background: #ffccbb&amp;quot; | No&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
| &lt;br /&gt;
| 0x00012C00&lt;br /&gt;
| 0x200&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
| See below.&lt;br /&gt;
| Console-unique encrypted New3DS key-storage, see below.&lt;br /&gt;
|-&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  twln&lt;br /&gt;
|  0x00012E00&lt;br /&gt;
|  0x08FB5200&lt;br /&gt;
|  &lt;br /&gt;
|  &lt;br /&gt;
| &lt;br /&gt;
|  0x03&lt;br /&gt;
|  TWL-NAND FAT16 File System. (DSi)&lt;br /&gt;
|-&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  twlp&lt;br /&gt;
|  0x09011A00&lt;br /&gt;
|  0x020B6600&lt;br /&gt;
|  &lt;br /&gt;
|  &lt;br /&gt;
| &lt;br /&gt;
|  0x03&lt;br /&gt;
|  TWL-NAND PHOTO FAT12 File System. (DSi)&lt;br /&gt;
|-&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  &lt;br /&gt;
|  0x0B100000&lt;br /&gt;
|  0x00030000&lt;br /&gt;
|  0x04&lt;br /&gt;
|  0x02&lt;br /&gt;
|  0x01&lt;br /&gt;
|  0x07&lt;br /&gt;
|  By default this partition is empty(only contains 0x00/0xFF bytes since it was never written to), when AGB_FIRM was never launched. This contains the AGB_FIRM GBA savegame.&lt;br /&gt;
|-&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  firm0&lt;br /&gt;
|  0x0B130000&lt;br /&gt;
|  0x00400000&lt;br /&gt;
|  0x03&lt;br /&gt;
|  0x02&lt;br /&gt;
|  0x02&lt;br /&gt;
|  0x06&lt;br /&gt;
|  [[FIRM|Firmware]] partition.&lt;br /&gt;
|-&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  firm1&lt;br /&gt;
|  0x0B530000&lt;br /&gt;
|  0x00400000&lt;br /&gt;
|  0x03&lt;br /&gt;
|  0x02&lt;br /&gt;
|  0x03&lt;br /&gt;
|  0x06&lt;br /&gt;
|  [[FIRM|Firmware]] partition.(Backup partition, same as above)&lt;br /&gt;
|-&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  style=&amp;quot;background: #ffccbb&amp;quot; | No&lt;br /&gt;
|  &lt;br /&gt;
|  0x0B930000&lt;br /&gt;
|  0x2F5D0000&lt;br /&gt;
|  0x01&lt;br /&gt;
|  0x02&lt;br /&gt;
|  0x04&lt;br /&gt;
|  0x04&lt;br /&gt;
|  CTR-NAND partition. (3DS)&lt;br /&gt;
|-&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  style=&amp;quot;background: #ffccbb&amp;quot; | No&lt;br /&gt;
|  nand&lt;br /&gt;
|  0x0B95CA00&lt;br /&gt;
|  0x2F3E3600&lt;br /&gt;
|  &lt;br /&gt;
|  &lt;br /&gt;
|  &lt;br /&gt;
|  0x04&lt;br /&gt;
|  CTR-NAND FAT16 File System.&lt;br /&gt;
|-&lt;br /&gt;
|  style=&amp;quot;background: #ffccbb&amp;quot; | No&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  &lt;br /&gt;
|  0x0B930000&lt;br /&gt;
|  0x41ED0000&lt;br /&gt;
|  0x01&lt;br /&gt;
|  0x03&lt;br /&gt;
|  0x04&lt;br /&gt;
|  0x05&lt;br /&gt;
|  CTR-NAND partition. (New3DS)&lt;br /&gt;
|-&lt;br /&gt;
|  style=&amp;quot;background: #ffccbb&amp;quot; | No&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  nand&lt;br /&gt;
|  0x0B95AE00&lt;br /&gt;
|  0x41D2D200&lt;br /&gt;
|  &lt;br /&gt;
|  &lt;br /&gt;
|  &lt;br /&gt;
|  0x05&lt;br /&gt;
|  CTR-NAND FAT16 File System. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
3DS TWL NAND FAT partitions has FAT volume name &amp;quot;TWL&amp;quot;, for CTR FAT partitions this is &amp;quot;CTR&amp;quot;. The offset/size for TWL partitions are stored in the MBR partition table, while the CTR partition table info is stored in the NAND NCSD header. Sector0 in the CTR-NAND partition contains a MBR partition table for the nand FAT16 filesystem, and the MBR signature at +0x1fe.&lt;br /&gt;
&lt;br /&gt;
NAND sectors which were never written to before only contain plaintext 0x00 or 0xFF bytes.&lt;br /&gt;
&lt;br /&gt;
None of the NAND partitions are normally accessible from the ARM11, except for twlp. CTR/TWL NAND FS can only be accessed when the exheader access control descriptor for those are enabled. Normally the CTR/TWL NAND descriptors are never enabled for retail ARM11 [[NCCH#CXI|CXI]] processes. The ARM11 can only access &amp;quot;nand:/rw/&amp;quot; mounted as the nandrw [[FS:OpenArchive|archive]], and &amp;quot;nand:/ro/&amp;quot; mounted as the nandro archive below.&lt;br /&gt;
&lt;br /&gt;
==== 0x4000 ====&lt;br /&gt;
On some 3DS systems(such as 3DS XL), there&#039;s a plaintext FAT16 boot record located at NAND offset 0x4000. This block does not exist for launch-day 3DS systems. This is the only plaintext block for this &amp;quot;partition&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
==== 0x12C00 ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x10&lt;br /&gt;
| Normal-key for keyslot 0x11, used for generating the rest of the New3DS keyslots&#039; keyX by decrypting various data with AES-ECB. With [[9.6.0-24|9.6.0-X]] this is only used for generating the keyX for keyslots 0x15 and 0x18.&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| 0x10&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]: Additional normal-key for keyslot 0x11, used for generating the keyX for keyslots 0x16 and 0x19..0x1F.&lt;br /&gt;
|-&lt;br /&gt;
| 0x20&lt;br /&gt;
| 0x1E0&lt;br /&gt;
| Not yet used as of New3DS FIRM [[9.6.0-24|9.6.0-X]].&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This 0x200-byte sector contains New3DS keys, this entire sector is encrypted with a console-unique keyX+keyY. The keyX+keyY for this is generated by the New3DS [[FIRM|arm9bin-loader]]. Once the arm9bin-loader finishes decrypting this data, the keyX+keyY in the keyslot are then cleared, then the memory used for generating the keydata is disabled(after it finishes using it for TWL key init).&lt;br /&gt;
&lt;br /&gt;
This entire sector is encrypted with AES-ECB, the entire plaintext sector is identical for all retail and dev New3DS systems (differing between the two).&lt;br /&gt;
&lt;br /&gt;
=CTR partition=&lt;br /&gt;
The structure of [[nand/title]] appears to be exactly the same as [[SD Filesystem|SD]], except savegames are stored under the [[System SaveData|nand/data/&amp;lt;ID0&amp;gt;/sysdata]] directory instead.&lt;br /&gt;
The sub-directory name under [[nand/data]] is the SHA256 hash over the [[nand/private/movable.sed|movable.sed]] keyY. This nand/data/&amp;lt;ID0&amp;gt; directory is the NAND equivalent of the &amp;quot;sdmc/Nintendo 3DS/&amp;lt;ID0&amp;gt;/&amp;lt;ID1&amp;gt;&amp;quot; directory, however the data contained here is stored in cleartext. The movable.sed keyY is only used for AES MACs for nand/data/&amp;lt;ID0&amp;gt;. The nand/data/&amp;lt;ID0&amp;gt;/extdata directory contains the shared [[extdata]], and is structured exactly the same way as SD extdata.&lt;br /&gt;
&lt;br /&gt;
 nand&lt;br /&gt;
 ├── __journal.nn_&lt;br /&gt;
 ├── [[nand/data|data]]&lt;br /&gt;
 │   └── &amp;lt;ID0&amp;gt;&lt;br /&gt;
 │       ├── [[Extdata|extdata]]          &lt;br /&gt;
 │       └── [[System SaveData|sysdata]]&lt;br /&gt;
 ├── [[Title Database|dbs]]&lt;br /&gt;
 ├── [[nand/fixdata|fixdata]]&lt;br /&gt;
 │   └── [[nand/fixdata/sysdata|sysdata]]&lt;br /&gt;
 ├── private&lt;br /&gt;
 │   └── [[nand/private/movable.sed|movable.sed]]&lt;br /&gt;
 ├── [[nand/ro|ro]]&lt;br /&gt;
 ├── [[nand/rw|rw]]&lt;br /&gt;
 ├── [[nand/ticket|ticket]] (This directory is empty since tickets are stored in [[Title Database|ticket.db]])&lt;br /&gt;
 ├── [[Title Data Structure|title]]&lt;br /&gt;
 └── [[nand/tmp|tmp]] (This is usually empty, even when installation for a system update still needs [[AMNet:FinishInstallToMedia|finalized]])&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;ro&amp;quot; and &amp;quot;rw&amp;quot; directories are accessible through the &amp;quot;nandrw&amp;quot; and &amp;quot;nandro&amp;quot; [[FS:OpenArchive|archives]], respectively. Their contents are as follows:&lt;br /&gt;
&lt;br /&gt;
 ro&lt;br /&gt;
 ├── [[nandro/private|private]]&lt;br /&gt;
 ├── [[nandro/shared|shared]]&lt;br /&gt;
 └── [[nandro/sys|sys]]&lt;br /&gt;
     ├── [[nandro/sys/HWCAL0.dat|HWCAL0.dat]]&lt;br /&gt;
     └── [[nandro/sys/HWCAL1.dat|HWCAL1.dat]]&lt;br /&gt;
 rw&lt;br /&gt;
 ├── [[nandrw/shared|shared]]&lt;br /&gt;
 └── [[nandrw/sys|sys]]&lt;br /&gt;
     ├── [[nandrw/sys/lgy.log|lgy.log]] (This is written to by [[FIRM|TWL_FIRM]] when errors occur, this is equivalent to native.log)&lt;br /&gt;
     ├── [[nandrw/sys/LocalFriendCodeSeed_B|LocalFriendCodeSeed_B]]&lt;br /&gt;
     ├── [[nandrw/sys/native.log|native.log]] (This is written to by [[ErrDisp]])&lt;br /&gt;
     ├── [[nandrw/sys/rand_seed|rand_seed]]&lt;br /&gt;
     ├── [[nandrw/sys/SecureInfo_A|SecureInfo_A]]&lt;br /&gt;
     └── [[nandrw/sys/updater.log|updater.log]]&lt;br /&gt;
&lt;br /&gt;
=TWL partition=&lt;br /&gt;
The structure of these TWL partitions is mostly the same as DSi, except tickets are stored in the CTR FAT FS. The twlp partition is exactly the same as DSi.&lt;br /&gt;
The structure of [[twln/title]] is exactly the same as CTR NAND/SD, except the .cmd file is a cleartext file.(This is likely a dummy file) The data directory under system titles&#039; /title directory does not exist, this likely only exists for DSiWare.&lt;br /&gt;
The directory names titleID-High used under [[twln/title]] is from DSi.&lt;br /&gt;
&lt;br /&gt;
 twln&lt;br /&gt;
 ├── [[twln/import/|import]]&lt;br /&gt;
 ├── [[twln/shared1/|shared1]]&lt;br /&gt;
 ├── [[twln/shared2/|shared2]]&lt;br /&gt;
 │   └── [[twln/shared2/0000|0000]]&lt;br /&gt;
 ├── [[twln/sys|sys]]&lt;br /&gt;
 │   ├── [[twln/sys/TWLFontTable.dat|TWLFontTable.dat]]&lt;br /&gt;
 │   └── [[twln/sys/log/|log]]&lt;br /&gt;
 │       ├── [[twln/sys/log/inspect.log|inspect.log]]&lt;br /&gt;
 │       └── [[twln/sys/log/product.log|product.log]]&lt;br /&gt;
 ├── [[twln/ticket/|ticket]]&lt;br /&gt;
 ├── [[twln/title/|title]]&lt;br /&gt;
 └── [[twln/tmp/|tmp]]&lt;br /&gt;
&lt;br /&gt;
 twlp&lt;br /&gt;
 └── [[twlp/photo/|photo]]&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=3DS_System_Flaws&amp;diff=23623</id>
		<title>3DS System Flaws</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=3DS_System_Flaws&amp;diff=23623"/>
		<updated>2025-07-24T16:47:40Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: More RO stuff&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Exploits are used to execute unofficial code (homebrew) on the Nintendo 3DS. This page is a list of publicly known system flaws, for userland applications/applets flaws see [[3DS_Userland_Flaws|here]].&lt;br /&gt;
&lt;br /&gt;
=Stale / Rejected Efforts=&lt;br /&gt;
* In the early days of 3DS hacking, Neimod was working on a RAM dumping setup for a while. He has de-soldered the 3DS&#039;s RAM chip and hooked it and the RAM pinouts on the 3DS&#039;s PCB up to a custom RAM dumping setup. He &#039;&#039;has&#039;&#039; published photos showing his setup to be working quite well, with the 3DS successfully booting up, but however, his flickr stream is now private along with most of his work and this method has been unreleased. RAM dumping can be done through homebrew now, making this method obsolete regardless.&lt;br /&gt;
&lt;br /&gt;
==Tips and info==&lt;br /&gt;
The 3DS uses the XN feature of the ARM11 processor. There&#039;s no official way from applications to enable executable permission for memory containing arbitrary unsigned code(there&#039;s a [[SVC]] for this, but only [[RO_Services|RO-module]] has access to it). A usable userland exploit would still be useful: you could only do return-oriented-programming with it initially. From ROP one could then exploit system flaw(s), see below.&lt;br /&gt;
&lt;br /&gt;
SD card [[extdata]] and SD savegames can be attacked, for consoles where the console-unique [[Nand/private/movable.sed|movable.sed]] was dumped(accessing SD data is far easier by running code on the target 3DS however).&lt;br /&gt;
&lt;br /&gt;
=System flaws=&lt;br /&gt;
== Hardware ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Fixed with hardware model/revision&lt;br /&gt;
!  Newest hardware model/revision this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| ARM9/ARM11 bootrom vectors point at uninitialized RAM&lt;br /&gt;
| ARM9&#039;s and ARM11&#039;s exception vectors are hardcoded to point at the CPU&#039;s internal memory (0x08000000 region for ARM9, AXIWRAM for ARM11). While the bootrom does set them up to point to an endless loop at some point during boot, it does not do so immediately. As such, a carefully-timed fault injection (via hardware) to trigger an exception (such as an invalid instruction) will cause execution to fall into ARM9 RAM. &lt;br /&gt;
Since RAM isn&#039;t cleared on boot (see below), one can immediately start execution of their own code here to dump bootrom, OTP, etc.&lt;br /&gt;
The ARM9 bootrom does the following at reset:  reset vector branches to another instruction, then branches to bootrom+0x8000. Hence, there&#039;s no way to know for certain when exactly the ARM9 exception-vector data stored in memory gets initialized.&lt;br /&gt;
&lt;br /&gt;
The vulnerable timing range is about 100 CPU cycles after they start (which happens after the PLLs have stabilized after power-up). A glitch needs to be injected during one of these 100 cycles for the attack to succeed.&lt;br /&gt;
&lt;br /&gt;
It has been exploited by derrek to dump the ARM9 bootrom as of Summer 2015.&lt;br /&gt;
| None: all available 3DS models at the time of writing have the exact same ARM9/ARM11 bootrom for the unprotected areas.&lt;br /&gt;
| New3DS&lt;br /&gt;
| End of February 2014&lt;br /&gt;
| [[User:Derrek|derrek]], WulfyStylez (May 2015) independently&lt;br /&gt;
|-&lt;br /&gt;
| Missing AES key clearing&lt;br /&gt;
| The hardware AES engine does not clear keys when doing a hard reset/reboot.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| August 2014&lt;br /&gt;
| Mathieulh/Others&lt;br /&gt;
|-&lt;br /&gt;
| No RAM clearing on reboots&lt;br /&gt;
| On an MCU-triggered reboot all RAM including FCRAM/ARM9 memory/AXIWRAM/VRAM keeps its contents.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| March 2014&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| 32bits of actual console-unique TWLNAND keydata&lt;br /&gt;
| On retail the 8-bytes at ARM9 address [[Memory_layout|0x01FFB808]] are XORed with hard-coded data, to generate the TWL console-unique keys, including TWLNAND. On Old3DS the high u32 is always 0x0, while on New3DS that u32 is always 0x2. On top of this, the lower u32&#039;s highest bit is always ORed. only 31 bits of the TWL console-unique keydata / TWL consoleID are actually console-unique.&lt;br /&gt;
This allows one to easily bruteforce the TWL console-unique keydata with *just* data from TWLNAND. On DSi the actual console-unique data for key generation is 8-bytes(all bytes actually set).&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| 2012?&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| DSi / 3DS-TWL key-generator&lt;br /&gt;
| After using the key generator to generate the normal-key, you could overwrite parts of the normal-key with your own data and then recover the key-generator output by comparing the new crypto output with the original crypto output. From the normal-key outputs, you could deduce the TWL key-generator function.&lt;br /&gt;
This applies to the keyX/keyY too.&lt;br /&gt;
&lt;br /&gt;
This attack does not work for the 3DS key-generator because keyslots 0-3 are only for TWL keys.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| 2011&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| 3DS key-generator&lt;br /&gt;
| The algorithm for generating the normal-keys for keyslots is cryptographically weak.  As a result, it is easily susceptible to differential cryptanalysis if the normal-key corresponding to any scrambler-generated keyslot is discovered.&lt;br /&gt;
&lt;br /&gt;
Several such pairs of matching normal-keys and KeyY values were found, leading to deducing the key-generator function.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| February 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]], [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| RSA keyslots don&#039;t clear exponent when setting modulus&lt;br /&gt;
| The [[RSA_Registers|RSA keyslots]] are set by boot ROM to have four private RSA keys.  The exponent value in the RSA registers is write-only and not readable.&lt;br /&gt;
&lt;br /&gt;
However, when setting a keyslot&#039;s modulus, the RSA hardware leaves the exponent alone.  This allows retrieving the exponent by doing a discrete logarithm of the output.&lt;br /&gt;
&lt;br /&gt;
By setting the modulus to a prime number whose modular multiplicative order is &amp;quot;smooth&amp;quot; (that is, p-1 is divisible by only small prime numbers), discrete logarithms can be calculated quickly using the [[wikipedia:Pohlig-Hellman algorithm|Pohlig-Hellman algorithm]].  If the prime chosen is greater than the modulus, but the same bit size, the discrete logarithm is the private exponent.&lt;br /&gt;
&lt;br /&gt;
This exploit&#039;s usefulness is limited: RSA keyslot 0 is only used in current firmware for deriving the 6.x save and 7.x NCCH keys, which were already known, and the other three keyslots are entirely unused.  Additionally, with a boot ROM dump, this exploit is moot; these private keys are located in the protected ARM9 boot ROM.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| March 2016&lt;br /&gt;
| [[User:Myria|Myria]]&lt;br /&gt;
|-&lt;br /&gt;
| [[CONFIG11_Registers#CFG11_GPUPROT|CFG11_GPUPROT]] allowing acccess to AXIWRAM/FCRAM-BASE-memregion&lt;br /&gt;
| [[CONFIG11_Registers#CFG11_GPUPROT|CFG11_GPUPROT]] can be configured by anything with access to it to allow the GPU to access the entire AXIWRAM+FCRAM. For example, this is an issue for any sysmodule that gets exploited and has access to this register memory-page(include one that&#039;s listed below).&lt;br /&gt;
&lt;br /&gt;
See also &amp;quot;kernelhax via gspwn&amp;quot; below.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| February 7, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Boot ROM ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Fixed with hardware model/revision&lt;br /&gt;
!  Newest hardware model/revision this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| FIRM partitions known-plaintext&lt;br /&gt;
| The [[Flash_Filesystem|FIRM partitions]] are encrypted with AES-CTR without a MAC. Since this works by XOR&#039;ing data with a static (per-console in this case) keystream, one can deduce the keystream of a portion of each FIRM partition if they have the actual FIRM binary stored in it.&lt;br /&gt;
&lt;br /&gt;
This can be paired with many exploits. For example, it allows minor FIRM downgrades (i.e. 10.4 to 9.6 or 9.5 to 9.4, but not 9.6 to 9.5).&lt;br /&gt;
However it is most commonly used to install arbitrary FIRMs (usually boot9strap), thanks to sighax.&lt;br /&gt;
&lt;br /&gt;
This can be somewhat addressed by having a FIRM header skip over previously used section offsets, but this would just air-gap newer FIRMs without fixing the core bug. This can also only be done a limited number of times due to the size of FIRM versus the size of the partitions.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| &lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| Boot9 AES keyinit function issues&lt;br /&gt;
| [[Bootloader|Boot9]] seems to have two bugs in the AES key-init function, see [[AES_Registers#AES_key-init|here]].&lt;br /&gt;
| None&lt;br /&gt;
| BootROM issue.&lt;br /&gt;
| 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| New3DS has same boot ROM as Old3DS&lt;br /&gt;
| The New3DS has the exact same boot ROM as the Old3DS.  This means, among other things, that all the same boot ROM flaws are present.  Also, this meant that it is possible to boot Old3DS firmware on New3DS (see &amp;quot;CFG9_SYSPROT9 bit1 not set by Kernel9&amp;quot;).&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| October 2014&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| sighax: Boot9 improper validation of FIRM partition RSA signatures&lt;br /&gt;
| The [[Flash_Filesystem|FIRM partitions]] are signed with RSA-2048 using SHA-256 and PKCS #1 v1.5 padding.  Boot9, however, improperly validates the padding in three ways:&lt;br /&gt;
# Boot9 permits block type 02, meant for encrypted messages, to be used for signatures.  Only 01, for signatures, should have been permitted.  As a result, when using block type 02, a signature block is not required to have a long string of FF bytes as padding, but rather any nonzero random values suffice.&lt;br /&gt;
# Boot9 does not require that the length of the padding fill out the signature block completely.  As a result, there is considerable freedom in the layout of a signature.&lt;br /&gt;
# Boot9 fails to do bounds checking in its parsing of the DER-encoded hash algorithm type and hash value; the length values given in DER are permitted to point outside the signature block.&lt;br /&gt;
Flaw 3 allows the DER encoding to be such that boot9 believes that the signature&#039;s hash value is outside the range of the block itself, somewhere on the stack.  This can be pointed at the correct hash value it computes.  Boot9 then memcmp&#039;s the calculated hash against itself, and thinks that the hash is valid.&lt;br /&gt;
&lt;br /&gt;
As a result of the above, we estimate that one in 2&amp;lt;sup&amp;gt;43&amp;lt;/sup&amp;gt; (~8.8 trillion) random fake signatures will be considered by Boot9 to be valid.  This is well within the range of brute force, particularly with an optimized GPU implementation.  An Nvidia GTX 1080 Ti would take about one week to find a match.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| July 2015&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| Boot9 FIRM loading doesn&#039;t blacklist memory-mapped I/O&lt;br /&gt;
| [[Bootloader|Boot9]]&#039;s FIRM loading blacklists Boot9 data regions, but forgets to do other important regions, including Memory-mapped I/O. Combined with sighax, a malicious FIRM can be used to overwrite:&lt;br /&gt;
a) boot9 data-abort handler, coupled with a 4th section that tries to NDMA copy to NULL, causing a data abort&lt;br /&gt;
&lt;br /&gt;
b) boot9 IRQ handler (this has the disadvantage that you must restore the original handler, then call it manually when your payload runs)&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| 2015(?)&lt;br /&gt;
| [[User:Derrek|derrek]] (2015?), [[User:Normmatt|Normmatt]] and [[User:SciresM|SciresM]] independently (January 2017).&lt;br /&gt;
|-&lt;br /&gt;
| &amp;quot;superhax&amp;quot;: Boot9 FIRM loading blacklist check is flawed&lt;br /&gt;
| Boot9 only makes sure the &#039;&#039;&#039;start&#039;&#039;&#039; and &#039;&#039;&#039;end&#039;&#039;&#039; address of each section is not covered by a blacklisted region. Thus, it is possible to overwrite blacklisted regions (e.g. ARM9 Exception Vectors) by choosing a FIRM section range that encloses an entire blacklisted region. The vulnerable code looks like this: if(blRegions[i].start &amp;lt;= sectionStart &amp;amp;&amp;amp; blRegions[i].end &amp;gt; sectionStart &amp;lt;nowiki&amp;gt;||&amp;lt;/nowiki&amp;gt; blRegions[i].start &amp;lt;= sectionEnd &amp;amp;&amp;amp; blRegions[i].end &amp;gt; sectionEnd) return false; // failure&lt;br /&gt;
The boot9 vector table (0x08000000) contains 6 entries, each 8-bytes wide (0x30 bytes); Only 0x08000000 through 0x08000040 are blacklisted, and boot9 doesn&#039;t use the region after the vector table (this is convenient because we can put any payload we want after it and not worry about overwriting chunks of boot9 code)&lt;br /&gt;
&lt;br /&gt;
To exploit this, craft a FIRM section payload that&#039;s loaded a few bytes before 0x08000000, add padding to get to 0x08000000 and overwrite the vector table; You could overwrite the data-abort vector and craft a 4th FIRM section that causes a data-abort OR you can just overwrite the IRQ function pointer at 0x08000004 (make sure your payload replaces the original boot9 function pointer); you can point the rest of the vectors to infinite loops since they shouldn&#039;t be triggered.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| August 2015&lt;br /&gt;
| [[User:Plutoo|plutoo]], [[User:Yellows8|yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ARM9 software ==&lt;br /&gt;
&lt;br /&gt;
=== arm9loader ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Public disclosure timeframe&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| Generating the keysector console-unique keys with ITCM+Boot9&lt;br /&gt;
| [[Bootloader|Boot9]] decrypts the 0x100-byte [[OTP_Registers|OTP]] using AES-CBC with keydata stored in Boot9. If hash verification is successful, the plaintext of the first 0x90-bytes are copied into [[Memory_layout|ITCM]]. This is the &#039;&#039;exact&#039;&#039; &#039;&#039;same&#039;&#039; region hashed by arm9loader when generating the console-unique keys for decrypting the keysector, except arm9loader uses the raw encrypted OTP.&lt;br /&gt;
&lt;br /&gt;
Therefore, with the OTP keydata+IV from Boot9 you can: encrypt the 0x90-bytes from ITCM, then hash the output to get the console-unique keys for the system&#039;s keysector. This can even be done for Old3DS which doesn&#039;t have the arm9loader keysector officially.&lt;br /&gt;
&lt;br /&gt;
It&#039;s unknown why arm9loader only used the first 0x90-bytes of OTP. Using more data from OTP would&#039;ve prevented this. Fixing this would require doing exactly that, but that would also mean updating the NAND keysector(which is dangerous).&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| &lt;br /&gt;
| 2015&lt;br /&gt;
| January 6, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Rearrangable keys in the NAND keystore&lt;br /&gt;
| Due to the keystore being encrypted with AES-ECB, one can rearrange blocks and still have the NAND keystore decrypt in a deterministic way. &lt;br /&gt;
&lt;br /&gt;
Using 10.0 FIRM it is possible to rearrange keys such that ARM9 memory is executed. As such using existing ARM9 execution 10.0 FIRM can be written to NAND and a payload written to memory, with the payload to be executed post-K9L using an MCU reboot.&lt;br /&gt;
| arm9loaderhax given existing ARM9 code execution&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| Early 2016&lt;br /&gt;
| 27 September 2016&lt;br /&gt;
| Myria, [[User:Dark samus|dark_samus]]; mathieulh (independently); [[User:Plutooo|plutoo]] (independently) + others&lt;br /&gt;
|-&lt;br /&gt;
| Uncleared OTP hash keydata in console-unique 0x11 key-generation&lt;br /&gt;
| Kernel9Loader does not clear the [[SHA_Registers#SHA_HASH|SHA_HASH register]] after use. As a result, the data stored here as K9L hands over to Kernel9 is the hash of [[OTP_Registers|OTP data]] used to seed the [[FIRM#New_3DS_FIRM|console-unique NAND keystore decryption key]] set on keyslot 0x11.&lt;br /&gt;
&lt;br /&gt;
Retrieving this keydata and the [[Flash_Filesystem#0x12C00|NAND keystore]] of the same device allows calculating the decrypted New3DS NAND keystore (non-unique, common to all New3DS units), which contains AES normal keys, also set on keyslot 0x11, which are then used to derive all current [[AES_Registers#Keyslots|New3DS-only AES keyXs]] including the newer batch introduced in [[9.6.0-24#arm9loader|9.6.0-X]]. From there, it is trivial to perform the same key derivation in order to initialize those keys on any system version, and even on Old3DS.&lt;br /&gt;
&lt;br /&gt;
This can be performed by exploiting the &amp;quot;arm9loaderhax&amp;quot; vulnerability to obtain post-K9L code execution after an MCU reboot (the bootrom section-loading fail is not relevant here, this attack was performed without OTP data by brute-forcing keys), and using this to dump the SHA_HASH register. This attack works on any FIRM version shipping a vulnerable version of K9L, whereas OTP dumping required a boot of &amp;lt;[[3.0.0-6|3.0.0-X]].&lt;br /&gt;
&lt;br /&gt;
This attack results in obtaining the entire (0x200-bytes) NAND keystore - it was confirmed at a later date that this keystore is encrypted with the same key (by comparing the decrypted data from multiple units), and therefore using another key in this store will not remedy the issue as all keys are known (i.e. later, unused keys decrypt to the same 0x200-bytes constant with the same OTP hash). Later keys could have been encrypted differently but this is not the case. As a result of this, it is not possible for Nintendo to use K9L again in its current format for its intended purpose, though this was not news from the moment people dumped a New3DS OTP.&lt;br /&gt;
| Derivation of all New3DS keys generated via the NAND keystore (0x1B &amp;quot;Secure4&amp;quot; etc.)&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| ~April 2015, implemented in May 2015&lt;br /&gt;
| 13 January 2016&lt;br /&gt;
| [[User:WulfyStylez|WulfyStylez]], [[User:Dazzozo|Dazzozo]], [[User:Shinyquagsire23|shinyquagsire23]] (complimentary + implemented), [[User:Plutooo|plutoo]], Normmatt (discovered independently)&lt;br /&gt;
|-&lt;br /&gt;
| enhanced-arm9loaderhax&lt;br /&gt;
| See the 32c3 3ds talk.&lt;br /&gt;
Since this is a combination of a trick with the arm9-bootrom + arm9loaderhax, and since you have to manually write FIRM to the firm0/firm1 NAND partitions, this can&#039;t be completely fixed. Any system with existing ARM9 code execution and an OTP/OTP hash dump can exploit this. Additionally, by using the FIRM partition known-plaintext bug and bruteforcing the second entry in the keystore, this can currently be exploited on all New3DS systems without any other prerequisite hacks.&lt;br /&gt;
| arm9loaderhax which automatically occurs at hard-boot.&lt;br /&gt;
| See arm9loaderhax / description.&lt;br /&gt;
| See arm9loaderhax / description.&lt;br /&gt;
| Theorized around mid July, 2015. Later implemented+tested by [[User:Plutooo|plutoo]] and [[User:Derrek|derrek]].&lt;br /&gt;
| 32c3 3ds talk (December 27, 2015)&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| arm9loaderhax: Missing verification block for the 9.6 keys&lt;br /&gt;
| Starting with [[9.6.0-24|9.6.0-X]] a new set of NAND-based keys were introduced. However, no verification block was added to verify that the new key read from NAND is correct. This was technically an issue from [[9.5.0-22|9.5.0-X]] with the original sector+0 keydata, however the below is only possible with [[9.6.0-24|9.6.0-X]] since keyslots 0x15 and 0x16 are generated from different 0x11 keyXs.&lt;br /&gt;
&lt;br /&gt;
Writing an incorrect key to NAND will cause arm9loader to decrypt the ARM9 kernel as garbage and then jump to it.&lt;br /&gt;
&lt;br /&gt;
This allows an hardware-based attack where you can boot into an older exploited firmware, fill all memory with NOP sleds/jump-instructions, and then reboot into executing garbage. By automating this process with various input keydata, eventually you&#039;ll find some garbage that jumps to your code.&lt;br /&gt;
&lt;br /&gt;
This gives very early ARM9 code execution (pre-ARM9 kernel). As such, it is possible to dump RSA keyslots with this and calculate the 6.x [[Savegames#6.0.0-11_Savegame_keyY|save]], and 7.x [[NCCH]] keys. This cannot be used to recover keys initialized by arm9loader itself. This is due to it wiping the area used for its stack during NAND sector decryption and keyslot init. &lt;br /&gt;
&lt;br /&gt;
Due to FIRMs on both Old and New 3DS using the same RSA data, this can be exploited on Old3DS as well, but only if one already has the actual plaintext normalkey from New3DS NAND sector 0x96 offset-0 and has dumped the OTP area of the Old3DS.&lt;br /&gt;
| Recovery of 6.x [[Savegames#6.0.0-11_Savegame_keyY|save key]]/7.x [[NCCH]] key, access to uncleared OTP hash keydata&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| arm9loader runs on Old3DS&lt;br /&gt;
| Despite being written only for New3DS, all of arm9loader runs fine on Old3DS.  It&#039;s not until booting Kernel9 that a New3DS FIRM partition would crash on an Old3DS.  As a result, if a bug exists in arm9loader to get control, it can be exploited on Old3DS by writing New3DS FIRM to the FIRM partitions.  Thus, arm9loaderhax works on both Old3DS and New3DS.&lt;br /&gt;
| arm9loader bugs also compromise Old3DS&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| Sometime in 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]] presumably&lt;br /&gt;
|-&lt;br /&gt;
| Uncleared New3DS keyslot 0x11&lt;br /&gt;
| Originally the New3DS [[FIRM]] arm9bin loader only cleared keyslot 0x11 when it gets executed at firmlaunch. This was fixed with [[9.5.0-22|9.5.0-X]] by completely clearing keyslot 0x11 immediately after the loader finishes using keyslot 0x11.&lt;br /&gt;
This means that any ARM9 code that can execute before the loader clears the keyslot at firmlaunch(including firmlaunch-hax) can get access to the uncleared keyslot 0x11, which then allows one to generate all &amp;lt;=v9.5 New3DS keyXs which are generated by keyslot 0x11.&lt;br /&gt;
&lt;br /&gt;
Therefore, to completely fix this the loader would have to generate more keys using different keyslot 0x11 keydata. This was done with [[9.6.0-24|9.6.0-X]].&lt;br /&gt;
| New3DS keyXs generation&lt;br /&gt;
| Mostly fixed with [[9.5.0-22|9.5.0-X]], completely fixed with new keys with [[9.6.0-24|9.6.0-X]].&lt;br /&gt;
| &lt;br /&gt;
| February 3, 2015 (one day after [[9.5.0-22|9.5.0-X]] release)&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Process9 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Public disclosure timeframe&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| Leak of normal-key matching a key-scrambler key&lt;br /&gt;
| New 3DS firmware versions [[8.1.0-0 New3DS|8.1.0]] through [[9.2.0-20|9.2.0]] set the encryption key for [[Amiibo]] data using a hardcoded normal-key in Process9.  In firmware [[9.3.0-21|9.3.0]], Nintendo &amp;quot;fixed&amp;quot; this by using the key scrambler instead, by calculating the keyY value for keyslot 0x39 that results in the same normal-key, then hardcoding that keyY into Process9.&lt;br /&gt;
&lt;br /&gt;
Nintendo&#039;s fix is actually the problem: Nintendo revealed the normal-key matching an unknown keyX and a known keyY.  Combined with the key scrambler using an insecure scrambling algorithm (see &amp;quot;Hardware&amp;quot; above), the key scrambler function could be deduced.&lt;br /&gt;
| Deducing the keyX for keyslot 0x39 and the key scrambler algorithm&lt;br /&gt;
| New 3DS [[9.3.0-21|9.3.0-X]], sort of&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| Sometime in 2015 after the hardware key-generator was broken.&lt;br /&gt;
| 32c3 3ds talk (December 27, 2015)&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Leak of normal-key matching a key-generator key&lt;br /&gt;
| During the 3DS&#039; development (June/July 2010) Nintendo added support installing encrypted content ([[CIA]]). Common-key index1 was intended to be a [[AES|hardware generated key]]. However while they added code to generate the key in hardware, they forgot to remove the normal-key for index1 (used elsewhere, likely old debug code). Nintendo later removed the normal key sometime before the first non-prototype firmware release.&lt;br /&gt;
&lt;br /&gt;
Knowing the keyY and the normal-key for common-key index1, the devkit key-generator algorithm can be deduced (see &amp;quot;Hardware&amp;quot; above). Additionally the remaining devkit common-keys can be generated once the common-key keyX is recovered.&lt;br /&gt;
&lt;br /&gt;
Note that the devkit key-generator was discovered to be the same as the retail key-generator.&lt;br /&gt;
| Deducing the keyX for keyslot 0x3D and hardware key-generator algorithm. Generate remaining devkit common-keys.&lt;br /&gt;
| pre-[[1.0.0-0|1.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| Shortly after the key-generator was revealed to be flawed at the 32c3 3ds talk&lt;br /&gt;
| January 20, 2016&lt;br /&gt;
| [[User:Jakcron|jakcron]]&lt;br /&gt;
|-&lt;br /&gt;
| Factory firmware is vulnerable to sighax&lt;br /&gt;
| During the 3DS&#039;s development, presumably boot9 was written (including the sighax vulnerability). This vulnerability is also present in factory firmware (and earlier, including 0.11). This was fixed in version 1.0.0-0.&lt;br /&gt;
| Deducing the mechanics of the sighax vulnerability in boot9 without having a dump of protected boot9. ARM9 code execution on factory/earlier firmware.&lt;br /&gt;
| [[1.0.0-0|1.0.0-X]]&lt;br /&gt;
| [[1.0.0-0|1.0.0-X]]&lt;br /&gt;
| May 9, 2017&lt;br /&gt;
| May 19, 2017&lt;br /&gt;
| [[User:SciresM|SciresM]], [[User:Myria|Myria]]&lt;br /&gt;
|-&lt;br /&gt;
| safecerthax &lt;br /&gt;
| O3DS &amp;amp; O2DS SAFE_FIRM is still vulnerable to the PXIAM:ImportCertificates flaw fixed in [[5.0.0-11]] and to SSLoth fixed in [[11.14.0-46]]. It makes it possible to spoof the official NUS update server and remotely trigger the vulnerability in SAFE_FIRM.&lt;br /&gt;
| Remote Arm9 code execution in O3DS/O2DS SAFE_FIRM&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2020&lt;br /&gt;
| December 18, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| twlhax: Corrupted SRL header leads to memory overwrite&lt;br /&gt;
| During TWL_FIRM boot, the ARM11 process TwlBg puts launcher.srl, the DSi bootloader, into FCRAM.  TWL_FIRM Process9 then parses the [http://dsibrew.org/wiki/NDS_Format SRL header] to place launcher.srl&#039;s code where DSi mode can execute it.&lt;br /&gt;
&lt;br /&gt;
DSi-mode memory is in FCRAM, but interleaved.  Each byte of DSi-mode memory also exists at some address in 3DS FCRAM space.&lt;br /&gt;
&lt;br /&gt;
Process9 does not validate the RSA signature on launcher.srl, unlike SRLs loaded from cartridge or NAND (DSiWare).  A compromised ARM11 can, in a manner similar to firmlaunchhax, send a launcher.srl with a modified SRL header.  By setting the SRL header&#039;s ARM7/ARM9 load addresses and sizes carefully, accounting for the different memory map and for DSi mode&#039;s interleaved memory, it is possible to overwrite part of Process9&#039;s stack and take control with a ROP chain.&lt;br /&gt;
&lt;br /&gt;
Fixed in 11.8.0-X by... (fill me in)&lt;br /&gt;
| ARM9 code execution (whilst still in 3DS mode)&lt;br /&gt;
| [[11.8.0-41|11.8.0-X]]&lt;br /&gt;
| [[11.8.0-41|11.8.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| August 11, 2018&lt;br /&gt;
| smea&lt;br /&gt;
|-&lt;br /&gt;
| agbhax&lt;br /&gt;
| This is the same issue as twlhax above. Legacy FIRMs share the same OS code (Arm9-side OS, Arm11 kernel), and therefore, the outdated AGB_FIRM can be tricked into executing the still vulnerable PrepareArm9ForTwl function.&lt;br /&gt;
| ARM9 code execution (whilst still in 3DS mode)&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| December 17, 2020&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| safefirmhax&lt;br /&gt;
| SAFE_MODE_FIRM is almost never updated(even when NATIVE_FIRM is updated for vuln fixes), this can be noticed by &#039;&#039;just&#039;&#039; checking 3dbrew/ninupdates title-listings.&lt;br /&gt;
&lt;br /&gt;
The fix for firmlaunch-hax was only applied to NATIVE_FIRM in [[9.5.0-22|9.5.0-X]], leaving SAFE_FIRM exploitable. With ARM11-kernel execution, one can trigger FIRM-launch in to SAFE_FIRM, do Kernel9 &amp;lt;=&amp;gt; Kernel11 sync, PXI sync and then repeat the original attack on SAFE_FIRM instead.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| 2012-2013?&lt;br /&gt;
| Wiki: January 2, 2017&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| safefirmhax 1.1&lt;br /&gt;
| Nintendo&#039;s original safefirmhax fix was flawed -- they added a global boolean that got set to true whenever a non-sysmodule title got launched (except for a hardcoded repair title id), and panic()&#039;d if that boolean was true to prevent launching safefirm after hax was active. However, because the boolean was initially false after firmlaunch -- With ARM11-kernel execution, one could FIRM-launch into NATIVE_FIRM, and then immediately FIRM-launch again into SAFE_FIRM early in NATIVE_FIRM boot before the boolean got set to true to repeat the safehax attack.&lt;br /&gt;
&lt;br /&gt;
This was fixed by adding additional CFG9_BOOTENV checks to firmlaunch code in 11.4.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[11.4.0-37|11.4.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| safefirmhax fix&lt;br /&gt;
| Wiki: April 10, 2017&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| ntrcardhax&lt;br /&gt;
| When reading the banner of a NTR title, Process9 relies on a hardware register to know when the banner was fully read.&lt;br /&gt;
However that register is shared between the ARM9 and the ARM11.&lt;br /&gt;
An attacker with k11 control can so make Process9 believe the banner continues forever and so trigger a buffer overflow.&lt;br /&gt;
With a custom banner for a NTR flashcart, this leads to code execution in Process9.&lt;br /&gt;
&lt;br /&gt;
This was fixed by adding bound checks on the read data.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[10.4.0-29|10.4.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| March 2015&lt;br /&gt;
| 32c3 3ds talk (December 27, 2015)&lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| Title downgrading via [[Application_Manager_Services|AM]]([[Application_Manager_Services_PXI|PXI]])&lt;br /&gt;
| When a title is *already* installed, Process9 will compare the installed title-version with the title-version being installed. When the one being installed is older, Process9 would return an error.&lt;br /&gt;
&lt;br /&gt;
However, this can be bypassed by just deleting the title first via the service command(s) for that: with the title removed from the [[Title_Database]], Process9 can&#039;t compare the input title-version with anything. Hence, titles can be downgraded this way.&lt;br /&gt;
&lt;br /&gt;
[[11.0.0-33|11.0.0-X]] fixed this for key system titles (MSET, Home Menu, spider, ErrDisp, SKATER, NATIVE_FIRM, and every retail system module), by checking the version of the title to install against a hard-coded list of (titleID, minimumVersionRequired) pairs.&lt;br /&gt;
| Bypassing title version check at installation, which then allows downgrading any title.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]], for key system titles.&lt;br /&gt;
| NATIVE_FIRM / AM-sysmodule [[11.0.0-33|11.0.0-X]]&lt;br /&gt;
| ?&lt;br /&gt;
| &lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| Anti-downgrade list did not include all system titles initially&lt;br /&gt;
| The anti-downgrade list did not include legacy FIRMs until [[11.8.0-41|11.8.0-X]]. Therefore, legacy FIRMs could still be downgraded.&lt;br /&gt;
| Downgrading legacy FIRMs; allowing to exploit bugs in older legacy FIRMs (of which at least one exists, see below).&lt;br /&gt;
| [[11.8.0-33|11.8.0]]&lt;br /&gt;
| [[11.8.0-33|11.8.0]]&lt;br /&gt;
| ?&lt;br /&gt;
| Wiki: August 5, 2018&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| TWL_FIRM cmd-9 unchecked offset&lt;br /&gt;
| In [[1.0.0-0|1.0.0-X]]&#039;s TWL_FIRM, cmds 8 and 9 were not stubbed (whereas in the corresponding NATIVE_FIRM, they were).&lt;br /&gt;
Command 8 does the Process9 initialisation for NTR carts if an NTR cart is inserted (NTR, not TWL, judged by chipid).&lt;br /&gt;
&lt;br /&gt;
Command 9 takes (u32 offset_read, u32 offset_write, u32 offset_read_end), and basically just copies (offset_read_end - offset_read) bytes starting at (offset_read) of [NTR cart header in arm9mem, NTR secure area in fcram, TWL secure area in fcram], to 0x18001000 + offset_write + offset_read.&lt;br /&gt;
&lt;br /&gt;
offset_write is not checked at all, thus this leads to ARM9 code execution as long as any NTR cart, including flashcarts that would normally be blocked by TWL_FIRM, is inserted.&lt;br /&gt;
&lt;br /&gt;
In [[2.0.0-2|2.0.0-X]] TWL_FIRM, those commands were stubbed out.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[2.0.0-2|2.0.0-X]]&lt;br /&gt;
| [[2.0.0-2|2.0.0-X]]&lt;br /&gt;
| January 2018&lt;br /&gt;
| Wiki: August 5, 2018&lt;br /&gt;
| [[User:Riley|Riley]]&lt;br /&gt;
|-&lt;br /&gt;
| FIRM launch doesn&#039;t check target FIRM version&lt;br /&gt;
| When executing a FIRM launch, Process9 doesn&#039;t validate that the target FIRM isn&#039;t an old version.  This allows booting an exploitable FIRM from a newer FIRM, if you can get the exploitable FIRM installed.  ([[11.0.0-33|11.0.0-X]] now prevents installing old versions of system titles, but this doesn&#039;t affect titles already installed.)&lt;br /&gt;
&lt;br /&gt;
This had a use after [[9.6.0-24|9.6.0-X]]: on a compromised 3DS running 9.2.0, you could install the 9.6.0 NATIVE_FIRM to FIRM0/FIRM1, but avoid putting it into the NATIVE_FIRM title.  This would boot the 9.2.0 system software but with the 9.6.0 Process9 and Kernel11.  With a user-mode exploit in a sufficiently-privileged application (e.g. mset), you could trigger a FIRM launch back to NATIVE_FIRM, which would load the 9.2.0 Process9 and Kernel11.&lt;br /&gt;
&lt;br /&gt;
9.6.0&#039;s keyslots 0x15 and 0x16 are unknown to 9.2.0, so 9.2.0 would not clear them.  You then could do firmlaunchhax against 9.2.0 to get ARM9 access with keyslots 0x15 and 0x16 set to their proper 9.6.0 values, allowing decrypting 9.6.0&#039;s encrypted titles.  Once the New3DS keystore was dumped, this became moot.&lt;br /&gt;
| Decrypting 9.6.0 NCCH files without dumping New3DS keystore&lt;br /&gt;
| None (but now moot)&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| August 12, 2018&lt;br /&gt;
| [[User:Yellows8|Yellows8]], [[User:Myria|Myria]]&lt;br /&gt;
|-&lt;br /&gt;
| FAT FS code null-deref&lt;br /&gt;
| When FSFile:Read is used with a file which is corrupted on a FAT filesystem(in particular SD), Process9 can crash. This particular crash is caused by a function returning NULL instead of an actual ptr due to an error. The caller of that function doesn&#039;t check for NULL which then triggers a read based at NULL.&lt;br /&gt;
&lt;br /&gt;
Sample &amp;quot;fsck.vfat -n -v -V &amp;lt;fat image backup&amp;gt;&amp;quot; output for the above crash:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;...&lt;br /&gt;
Starting check/repair pass.&lt;br /&gt;
&amp;lt;FilePath0&amp;gt; and&lt;br /&gt;
&amp;lt;FilePath1&amp;gt;&lt;br /&gt;
 share clusters.&lt;br /&gt;
 Truncating second to 3375104 bytes.&lt;br /&gt;
&amp;lt;FilePath1&amp;gt;&lt;br /&gt;
 File size is 2787392 bytes, cluster chain length is 16384 bytes.&lt;br /&gt;
 Truncating file to 16384 bytes.&lt;br /&gt;
Checking for unused clusters.&lt;br /&gt;
Reclaimed 1 unused cluster (16384 bytes).&lt;br /&gt;
Checking free cluster summary.&lt;br /&gt;
Free cluster summary wrong (1404490 vs. really 1404491)&lt;br /&gt;
 Auto-correcting.&lt;br /&gt;
Starting verification pass.&lt;br /&gt;
Checking for unused clusters.&lt;br /&gt;
Leaving filesystem unchanged.&amp;lt;/pre&amp;gt;&lt;br /&gt;
| Useless null-based-read&lt;br /&gt;
| None&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| July 8-9, 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[FS:EnumerateExtSaveData]] crashes process9 when trying to parse a file as an extdata directory in Data Management (MSET9)&lt;br /&gt;
| In the implementation for FSPXI:EnumerateExtSaveData (called by [[System_Settings|MSET]] to parse 3DS extdata IDs for Data Management), the return value of the P9 internal function call to open a directory (when enumerating contents of the extdata directory) was not checked. Therefore, if the call fails, an uninitialised pointer on stack will be used for a vtable call.&lt;br /&gt;
&lt;br /&gt;
As such, a file that starts with 8 hex digits can crash process9 if placed directly inside the extdata directory. It can crash in various ways based on subtle differences in the way the user triggers the crash event.&lt;br /&gt;
&lt;br /&gt;
While mostly leading to null derefs, in one specific context, process9 jumps directly to an ID1 string being held in ARM9 memory. Surprisingly, the 3DS doesn&#039;t discern what characters are used for the ID1 directory name on the SD, only requiring exactly 32 chars. This allows the attacker to insert arm instructions into the unicode ID1 dirname and take control of the ARM9, and thus, full control of the 3DS.&lt;br /&gt;
| ARM9 code execution (primary)&lt;br /&gt;
| None&lt;br /&gt;
| [[11.17.0-50|11.17.0-X]]&lt;br /&gt;
| April 2022&lt;br /&gt;
| August 7, 2023&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| RSA signature padding checks&lt;br /&gt;
| The TWL_FIRM RSA sig padding check code used for all TWL RSA sig-checks has issues, see [[FIRM|here]].&lt;br /&gt;
The main 3DS RSA padding check code(non-certificate, including NATIVE_FIRM) uses the function used with the above to extract more padding + the actual hash from the additional padding. This isn&#039;t really a problem here because there&#039;s proper padding check code which is executed prior to this.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.5.0-22|9.5.0-X]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[AMPXI:ValidateDSiWareSectionMAC]] [[AES_Registers|AES]] keyslot reuse&lt;br /&gt;
| When the input DSiWare section index is higher than &amp;lt;max number of DSiWare sections supported by this FIRM&amp;gt;, Process9 uses keyid 0x40 for calculating the AESMAC, which translates to keyslot 0x40. The result is that the keyslot is left at whatever was already selected before, since the AES selectkeyslot code will immediately  return when keyslot is &amp;gt;=0x40. However, actually exploiting this is difficult: the calculated AESMAC is never returned, this command just compares the calculated AESMAC with the input AESMAC(result-code depends on whether the AESMACs match). It&#039;s unknown whether a timing attack would work with this.&lt;br /&gt;
This is basically a different form of the pxips9 keyslot vuln, except with AESMAC etc.&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| March 15, 2015&lt;br /&gt;
| December 29, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| pxips9 [[AES_Registers|AES]] keyslot reuse&lt;br /&gt;
| This requires access to the [[Process_Services|ps:ps]]/pxi:ps9 services. One way to get access to this would be snshax on system-version &amp;lt;=10.1.0-X(see 32c3 3ds talk).&lt;br /&gt;
When an invalid key-type value is passed to any of the PS commands, Process9 will try to select keyslot 0x40. That aesengine_setkeyslot() code will then immediately return due to the invalid keyslot value. Since that function doesn&#039;t return any errors, Process9 will just continue to do crypto with whatever AES keyslot was selected before the PS command was sent.&lt;br /&gt;
| Reusing the previously used keyslot, for crypto with PS.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| Roughly the same time(same day?) as firmlaunch-hax.&lt;br /&gt;
| December 29, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| firmlaunch-hax: FIRM header ToCToU&lt;br /&gt;
| This can&#039;t be exploited from ARM11 userland.&lt;br /&gt;
During [[FIRM]] launch, the only FIRM header the ARM9 uses at all is stored in FCRAM, this is 0x200-bytes(the actual used FIRM RSA signature is read to the Process9 stack however). The ARM9 doesn&#039;t expect &amp;quot;anything&amp;quot; besides the ARM9 to access this data.&lt;br /&gt;
With [[9.5.0-22]] the address of this FIRM header was changed from a FCRAM address, to ARM9-only address 0x01fffc00.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[9.5.0-22]]&lt;br /&gt;
| &lt;br /&gt;
| 2012, 3 days after [[User:Yellows8|Yellows8]] started Process9 code RE.&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Uninitialized data output for (PXI) command replies&lt;br /&gt;
| PXI commands for various services(including some [[Filesystem_services_PXI|here]] and many others) can write uninitialized data (like from ARM registers) to the command reply. This happens with stubbed commands, but this can also occur with certain commands when returning an error.&lt;br /&gt;
Certain ARM11 service commands have this same issue as well.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.3.0-21|9.3.0-X]]&lt;br /&gt;
| ?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Filesystem_services_PXI|FSPXI]] OpenArchive SD permissions&lt;br /&gt;
| Process9 does not use the exheader ARM9 access-mount permission flag for SD at all.&lt;br /&gt;
This would mean ARM11-kernelmode code / fs-module itself could directly use FSPXI to access SD card without ARM9 checking for SD access, but this is rather useless since a process is usually running with SD access(Home Menu for example) anyway.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.3.0-21|9.3.0-X]]&lt;br /&gt;
| 2012&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[AMPXI:ExportDSiWare]] export path&lt;br /&gt;
| Process9 allocates memory on Process9 heap for the export path then verifies that the actual allocated size matches the input size. Then Process9 copies the input path from FCRAM to this buffer, and uses it with the Process9 FS openfile code, which use paths in the form of &amp;quot;&amp;lt;mountpoint&amp;gt;:/&amp;lt;path&amp;gt;&amp;quot;.&lt;br /&gt;
Process9 does not check the contents of this path at all before passing it to the FS code, besides writing a NUL-terminator to the end of the buffer.&lt;br /&gt;
| Exporting of DSiWare to arbitrary Process9 file-paths, such as &amp;quot;nand:/&amp;lt;path&amp;gt;&amp;quot; etc. This isn&#039;t really useful since the data which gets written can&#039;t be controlled.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.5.0-22]]&lt;br /&gt;
| April 2013&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[DSiWare_Exports]] [[CTCert]] verification&lt;br /&gt;
| Just like DSi originally did, 3DS verifies the APCert for DSiWare on SD with the CTCert also in the DSiWare .bin. On DSi this was fixed with with system-version 1.4.2 by verifying with the actual console-unique cert instead(stored in NAND), while on 3DS it&#039;s still not fixed.&lt;br /&gt;
On 3DS this is used in conjunction with seedminer to be able to decrypt &amp;amp; modify DSiWare TAD containers and inject them with exploitable DSiWare titles such as sudoku (sudokuhax) and Flipnote JPN (ugopwn)&lt;br /&gt;
| When the movable.sed keyY for the target 3DS is known and the target 3DS CTCert private-key is unknown, importing of modified DSiWare SD .bin files.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.10.0-X&lt;br /&gt;
| April 2013&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| seedminer: movable.sed keyY vulnerable to brute-force&lt;br /&gt;
| Half of the movable.sed keyY&#039;s 128 bits are leaked through the [[Nandrw/sys/LocalFriendCodeSeed_B|LFCS]], which is available in userland and below. The LFCS itself also leaks almost half of the remaining bits by following the ratio: u32 keyY[3]=1/5(LFCS). The remaining keyY[3] uncertainty of about ±2000 can be greatly reduced by plotting expected error margins with several keyYs. This results in a final uncertainty of about 2^40, easily within practical brute force range of an average modern PC.&lt;br /&gt;
| Knowing the keyY of a given 3ds allows for modification of DSiWare export contents, and chained with several other public vulns, ultimately arm9 execution.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.8.0-X&lt;br /&gt;
| December 2017&lt;br /&gt;
| January 2018&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| Improper validation of DSiWare title SRLs&lt;br /&gt;
| The 3DS does not verify if the actual SRL embedded in the title&#039;s directory matches the titleID in the TMD before launching it or importing it from an sd DSiWare export. &lt;br /&gt;
| This allows embedding older, exploitable DSiWare titles in completely different, unexploitable DSiWare titles. Since DSiWare has raw NAND RW, this can result in arm9 control through FIRM known-plaintext and sighax attacks.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.10.0-X&lt;br /&gt;
| 2015?&lt;br /&gt;
| December 2016&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| DSiWare import/export functions allow TWL system titles as arguments&lt;br /&gt;
| AM ImportTwlBackup/ExportTwlBackup unnecessarily allow TWL system titles such as DS Download Play to import/export from userland and System Settings -&amp;gt; Data Management (only am:sys is needed for userland). This is difficult to abuse for dsihax injection because no TWL system title has a save file, and any import with a save included will result in FS err C8804464. However, there is at least one dsihax primary that can load a payload from a non-NAND source, and not error if it can&#039;t access its public.sav (JPN Flipnote Studio v0).&lt;br /&gt;
| When combined with other public vulns, arm9 code execution.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.10.0-X&lt;br /&gt;
| May 2018&lt;br /&gt;
| Sept 2018&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| [[Gamecard_Services_PXI]] unchecked REG_CTRCARDCNT transfer-size&lt;br /&gt;
| The u8 REG_CTRCARDCNT transfer-size parameter for the [[Gamecard_Services_PXI]] read/write CTRCARD commands is used as an index for an array of u16 values. Before [[5.0.0-11|5.0.0-X]] this u8 value wasn&#039;t checked, thus out-of-bounds reads could be triggered(which is rather useless in this case).&lt;br /&gt;
| Out-of-bounds read for a value which gets written to a register.&lt;br /&gt;
| [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| 2013?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[PXI_Registers|PXI]] cmdbuf buffer overrun&lt;br /&gt;
| The Process9 code responsible [[PXI_Registers|PXI]] communications didn&#039;t verify the size of the incoming command before writing it to a C++ member variable. &lt;br /&gt;
| Probably ARM9 code execution&lt;br /&gt;
| [[5.0.0-11|5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| March 2015, original timeframe if any unknown&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]]/[[User:Yellows8|Yellows8]]/maybe others(?)&lt;br /&gt;
|-&lt;br /&gt;
| [[Application_Manager_Services_PXI|PXIAM]]:ImportCertificates (See also [[Application_Manager_Services|this]])&lt;br /&gt;
| When handling this command, Process9 allocates a 0x2800-byte heap buffer, then copies the 4 FCRAM input buffers to this heap buffer without checking the sizes at all(only the buffers with non-zero sizes are copied). Starting with [[5.0.0-11|5.0.0-X]], the total combined size of the input data must be &amp;lt;=0x2800.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| May 2013&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Process_Services_PXI|PS RSA]] commands buffer overflows&lt;br /&gt;
| pxips9 cmd1(not accessible via ps:ps) and VerifyRsaSha256: unchecked copy to a buffer in Process9&#039;s .bss, from the input FCRAM buffer. The buffer is located before the pxi cmdhandler threads&#039; stacks. SignRsaSha256 also has a buf overflow, but this isn&#039;t exploitable.&lt;br /&gt;
The buffer for this is the buffer for the signature data. With v5.0, the signature buffer was moved to stack, with a check for the signature data size. When the signature data size is too large, Process9 uses [[SVC|svcBreak]].&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[PXI_Registers|PXI]] pxi_id bad check&lt;br /&gt;
| The Process9 code responsible for [[PXI_Registers|PXI]] communications read pxi_id as a signed char. There were two flaws:&lt;br /&gt;
* They used it as index to a lookup-table without checking the value at all.&lt;br /&gt;
* Another function verified that pxi_id &amp;lt; 7, allowing negative values to pass the check. This would also cause an out-of-range table-lookup.&lt;br /&gt;
| Maybe ARM9 code execution&lt;br /&gt;
| [[3.0.0-5|3.0.0-5]]&lt;br /&gt;
|&lt;br /&gt;
| March 2015, originally 2012 for the first issue at least&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]], maybe others(?)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Kernel9 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[CONFIG Registers#CFG9_SYSPROT9|CFG9_SYSPROT9]] bit1 not set by Kernel9&lt;br /&gt;
| Old versions of Kernel9 never set bit1 of [[CONFIG Registers#CFG9_SYSPROT9|CFG9_SYSPROT9]]. This leaves the [[OTP Registers|0x10012000]]-region unprotected (this region should be locked early during boot!). Since it&#039;s never locked, you can dump it once you get ARM9 code execution.&lt;br /&gt;
&lt;br /&gt;
From [[3.0.0-5|3.0.0-X]] this was fixed by setting the bit in Kernel9 after poking some registers in that region. On New3DS arm9loader sets this bit instead of Kernel9, which is exploitable through a hardware + software vulnerability (see arm9loaderhax / description).&lt;br /&gt;
&lt;br /&gt;
This flaw resurged when it gained a new practical use: retrieving the OTP data for a New3DS console in order to decrypt the key data used in arm9loader (see enhanced-arm9loaderhax / description). This was performed by downgrading to a vulnerable system version. By accounting for differences in CTR-NAND crypto (0x05 -&amp;gt; 0x04, see partition encryption types [[Flash_Filesystem#NAND_structure|here]]) and using an Old3DS [[NCSD#NCSD_header|NCSD Header]], it is possible to boot a New3DS using Old3DS firmware 1.0-2.x to retrieve the required OTP data using this flaw.&lt;br /&gt;
| Dumping the [[OTP Registers|OTP]] area.&lt;br /&gt;
Decrypting New3DS sector 0x96 keyblock.&lt;br /&gt;
| [[3.0.0-5|3.0.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| February 2015&lt;br /&gt;
| [[User:Plutooo|plutoo]], Normmatt independently&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ARM11 software ==&lt;br /&gt;
=== Kernel11 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcUnbindInterrupt]] double free when irqId = 15&lt;br /&gt;
| svcBindInterrupt and svcUnbindInterrupt give special treatment to irqId 15 (FIQ helper): the access control list is bypassed and the provided KInterruptEvent (event or semaphore, via handle) is stored inside a singleton static object after having its refcount increased by 1.&lt;br /&gt;
&lt;br /&gt;
svcUnbindInterrupt assumes that the user-provided handle is what is stored in the singleton and will decref the user-provided KInterruptEvent twice, causing a use-after-free if the attacker didn&#039;t actually provide an handle to the same event or semaphore.&lt;br /&gt;
&lt;br /&gt;
This was &amp;quot;fixed&amp;quot; on [[11.14.0-46|11.14.0-X]] by preventing irqId 15 to be bound on retail units altogether (in both functions).&lt;br /&gt;
| Arm11 kernel code execution&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]] (only on retail units)&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2019&lt;br /&gt;
| [[User:TuxSH|TuxSH]], maybe others&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcKernelSetState]] op=3 could map the NULL page&lt;br /&gt;
| svcKernelSetState op=3 param1=1 maps the firmlaunch parameters page to the user-specified VA.&lt;br /&gt;
&lt;br /&gt;
It had previously no check, allowing the attacker to map data at VA 0.&lt;br /&gt;
&lt;br /&gt;
Starting from [[11.14.0-46|11.14.0-X]], the VA must be in the standard 0x10000000-0x14000000 address range.&lt;br /&gt;
| Mapping the NULL page (as RW) to leverage other kernel vulnerabilities&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2019&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcMapProcessMemory]] can map the NULL page&lt;br /&gt;
| svcMapProcessMemory&#039;s destination VA is unchecked.&lt;br /&gt;
&lt;br /&gt;
By passing a big enough &amp;quot;size&amp;quot; parameter, an attacker can map chunks of data at VA 0 in the destination (caller) process.&lt;br /&gt;
| Mapping the NULL page (as RW) to leverage other kernel vulnerabilities&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2020&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| Resource limit use-after-free&lt;br /&gt;
| When assigning a KResourceLimit to a KProcess, the reslimit&#039;s refcounter doesn&#039;t get incremented. This essentially means all KResourceLimit get freed if pm gets somehow terminated.&lt;br /&gt;
&lt;br /&gt;
It turns out it is possible to ask pm (via ns:s or pm:app) to terminate itself along all other KIPs simply by passing TID 0004000100001000.&lt;br /&gt;
&lt;br /&gt;
Calling [[SVC|svcGetResourceLimit]] afterwards triggers a use-after-free. This is rather difficult to exploit, however: there is one slot left in the reslimit slabheap. An attacker either has to map the NULL page as R(W)X (svcControlProcessMemory vuln fixed on [[11.8.0-41|11.8.0-X]]), or use one of the map-null exploits above while having access to svcCreateResourceLimit (with the only one that is easy enough to use in that context having been fixed on [[11.14.0-46|11.14.0-X]], anyway).&lt;br /&gt;
| Arm11 kernel code execution&lt;br /&gt;
| None (although near impossible to exploit on [[11.14.0-46|11.14.0-X]])&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2020&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcSetProcessIdealProcessor]] reference count overflow and therefore use-after-free.&lt;br /&gt;
| The SVC receive two arguments: handle and idealprocessor. The handle is used to get the KProcess object and the KProcess-&amp;gt;refCnt gets incremented,later the function check if the KProcess-&amp;gt;mem_type != BASE and if yes, it checks for idealprocessor == 2 or idealprocessor != 3. The problem here is that if you pass the idealprocessor = 3 it won&#039;t meet any condition and return the error 0xD9001BEA without decrement the reference count. &lt;br /&gt;
It can be abused to overflow the KProcess reference count that will lead to an Use-after-free. &lt;br /&gt;
| Before [[11.2.0-35|11.2.0-X]]: reference count overflow and therefore use-after-free.&lt;br /&gt;
| &lt;br /&gt;
| [[11.6.0-39|11.6.0-X]]&lt;br /&gt;
| November 2, 2017&lt;br /&gt;
| [[User:st4rk|st4rk]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcGetThreadList]] process reference leak&lt;br /&gt;
| When given a valid process handle (including &amp;lt;code&amp;gt;0xFFFF8001&amp;lt;/code&amp;gt;), svcGetThreadList forgets to decrement the reference count of the underlying [[KProcess]] instance, after having finished using it.&lt;br /&gt;
| Before [[11.2.0-35|11.2.0-X]]: reference count overflow and therefore use-after-free, but this UAF was most likely not exploitable&lt;br /&gt;
| &lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| April 3, 2017&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| kernelhax via gspwn&lt;br /&gt;
| Originally the kernel didn&#039;t initialize [[CONFIG11_Registers#CFG11_GPUPROT|CFG11_GPUPROT]]. Since it&#039;s 0 at hard-boot, this allowed the GPU to access the entire FCRAM + AXIWRAM.&lt;br /&gt;
| Entire FCRAM+AXIWRAM R/W.&lt;br /&gt;
| [[3.0.0-5|3.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| February 7, 2017&lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]] partly&lt;br /&gt;
|-&lt;br /&gt;
| fasthax&lt;br /&gt;
| When a KTimer is created in pulse mode, the kernel calls a virtual function to reset the timer each time it pulses. The scheduler is locked for that core to avoid race conditions, but another core can call CloseHandle on the timer and free it, leading to a UAF vtable call.&lt;br /&gt;
| See description.&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| May 2016&lt;br /&gt;
| nedwill&lt;br /&gt;
|-&lt;br /&gt;
| ipctakeover&lt;br /&gt;
| When sending cmdreplies, it does not validate that the src_addr and src_size match the equivalent dst_addr and dst_size. With a modified addr/size specified in a cmdreply for an output buffer, the data-copy for the first/last pages could be used to overwrite data outside of the buffer specified by the original process.&lt;br /&gt;
&lt;br /&gt;
Used by ctr-httpwn as of v1.2, for &amp;quot;ipctakeover/bosshaxx&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This can be used to takeover processes where the process is using your service session. Like HTTPC -&amp;gt; BOSS, for bosshaxx above. NIM takeover can be done too(actual stack buffer overflow can trigger), etc.&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| November 26, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Using IPC input buffers as output buffers&lt;br /&gt;
| When sending cmdreplies, it does not validate that the cmdreply descriptor type matches the equivalent cmdreq descriptor type. This could be used by an exploited sysmodule to use what was intended as an input-buffer as an output-buffer, and also combine other IPC vuln(s) with this.&lt;br /&gt;
&lt;br /&gt;
Used by ctr-httpwn as of v1.2, for &amp;quot;ipctakeover/bosshaxx&amp;quot;.&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| November 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
|  [[SVC]] table too small&lt;br /&gt;
|  The table of function pointers for SVC&#039;s only contains entries up to 0x7D, but the biggest allowed SVC for the table is 0x7F. Thus, executing SVC7E or SVC7F would make the SVC-handler read after the buffer, and interpret some ARM instructions as function pointers.&lt;br /&gt;
&lt;br /&gt;
However, this would require patching the kernel .text or modifying SVC-access-control. Even if you could get these to execute, they would still jump to memory that isn&#039;t mapped as executable.&lt;br /&gt;
| &lt;br /&gt;
|  None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| 2012&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
|  [[SVC|svcBackdoor (0x7B)]]&lt;br /&gt;
|  This backdoor allows executing SVC-mode code at the user-specified code-address. This is used by Process9, using this on the ARM11 (with NATIVE_FIRM) required patching the kernel .text or modifying SVC-access-control.&lt;br /&gt;
| See description&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]] (deleted)&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| veryslowpidhax&lt;br /&gt;
| &#039;&#039;&#039;This is completely different from the kernelmode-code-execution vuln described in the below separate entry.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
When updating the kernel global PID counter under [[SVC|svcCreateProcess]] the kernel does not check for wraparound to 0x0(the PID for the very first process). This only matters because [[Services|SM-module]] allows processes with PID value less than &amp;lt;total ARM11 FIRM modules&amp;gt; to access &#039;&#039;all&#039;&#039; services, without checking exheader service-access-control; and because Kernel11 checks for the PID to be 1 (loader) to use the input mem-region value on ControlMemory. This alone does not affect access the [[SVC|SVCs]] access table at all.&lt;br /&gt;
&lt;br /&gt;
Inlined ldrex+strex code is used for updating the above counter. [[11.2.0-35|11.2.0-X]] had changes for similar code, but it was only for dedicated ldrex+strex functions(mainly for kernel objects) and hence this PID code was not affected.&lt;br /&gt;
&lt;br /&gt;
With launching+terminating a sysmodule repeatedly with this via ns:s, it would take weeks to finish(if not at least about a month?).&lt;br /&gt;
| Access to all [[Services_API|services]], ControlMemory on any given mem-region.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| 2012 maybe?&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
|  slowhax/waithax&lt;br /&gt;
|  svcWaitSynchronizationN does not decrement the references to valid handles in an array before returning an error when it encounters an invalid handle. This allows one to (slowly) overflow the reference count for a handle object to zero.&lt;br /&gt;
| ARM11 kernel-mode code execution&lt;br /&gt;
| [[11.2.0-35|11.2.0-X]]&lt;br /&gt;
| [[11.2.0-35|11.2.0-X]]&lt;br /&gt;
| 2016&lt;br /&gt;
| nedwill, [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Memory_layout#ARM11_Detailed_virtual_memory_map|0xEFF00000]] / 0xDFF00000 ARM11 kernel virtual-memory&lt;br /&gt;
| The ARM11 kernel-mode 0xEFF00000/0xDFF00000 virtual-memory(size 0x100000) is mapped to phys-mem 0x1FF00000(entire DSP-mem + entire AXIWRAM), with permissions RW-. This is used during ARM11 kernel startup for loading the FIRM-modules from the FIRM section located in DSP-mem, this never seems to be used after that, however. This is never unmapped either.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| memchunkhax2.1&lt;br /&gt;
| Nintendo&#039;s fix for memchunkhax2 in [[10.4.0-29|10.4.0-X]] did not fix the GPU case: one may cause the requisite ToCToU race using gspwn, bypassing the new validation.&lt;br /&gt;
derrek&#039;s original 32c3 presentation for memchunkhax2 commented that a GPU-based attack was possible, but would be difficult.  However, memchunkhax2.1 showed that it was possible to do fairly reliably.&lt;br /&gt;
| ARM11 kernel code execution&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]], via the new [[Memory_Management#MemoryBlockHeader|memchunkhdr]] MAC which prevents modifying memchunkhdr data with DMA.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| [[User:Derrek|derrek]], aliaspider&lt;br /&gt;
|-&lt;br /&gt;
| memchunkhax2&lt;br /&gt;
| When allocating a block of memory, the &amp;quot;next&amp;quot; pointer of the [[Memory_Management#MemoryBlockHeader|memchunkhdr]] is accessed without being checked after being mapped to userland.&lt;br /&gt;
This allows a race condition, where the process can change the next pointer just before it&#039;s accessed. By pointing the next pointer to a crafted memchunckhdr in the kernel SlabHeap, some of the SlabHeap is allocated to the calling process, allowing to change vtables of kernel objects. &lt;br /&gt;
| ARM11 kernel code execution&lt;br /&gt;
| [[10.4.0-29|10.4.0-X]] (partially, see memchunkhax2.1)&lt;br /&gt;
| [[10.4.0-29|10.4.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| heaphax&lt;br /&gt;
| Can change the size of free memchunk structures stored in FCRAM using DMA, which leads to the ability to allocate memory chunks over already-allocated memory. This can be used in the SYSTEM region to allocate RW memory over any part of the NS system module, which is enough to take it over.&lt;br /&gt;
| Code execution with access to all of NS&#039;s privileges. (including downgrading) Code execution within any applet.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]], via the new [[Memory_Management#MemoryBlockHeader|memchunkhdr]] MAC which prevents modifying memchunkhdr data with DMA.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]]&lt;br /&gt;
| April 2015 ?&lt;br /&gt;
| smea&lt;br /&gt;
|-&lt;br /&gt;
| snshax&lt;br /&gt;
| Can force creation of Safe NS process into gspwn-able memory, allowing for takeover.&lt;br /&gt;
| Code execution with access to all of NS&#039;s privileges. (including downgrading)&lt;br /&gt;
| [[10.1.0-27|10.1.0-X]]&lt;br /&gt;
| [[10.1.0-27|10.1.0-X]]&lt;br /&gt;
| April 2015 ?&lt;br /&gt;
| smea&lt;br /&gt;
|-&lt;br /&gt;
|  AffinityMask/processorid validation&lt;br /&gt;
|  With [[10.0.0-27|10.0.0-X]] the following functions were updated: svcGetThreadAffinityMask, svcGetProcessAffinityMask, svcSetProcessAffinityMask, and svcCreateThread. The code changes for all but svcCreateThread are identical.&lt;br /&gt;
The original code with the first 3 did the following: &lt;br /&gt;
* if(u32_processorcount &amp;gt; ~0x80000001)return 0xe0e01bfd;&lt;br /&gt;
* if(s32_processorcount &amp;gt; &amp;lt;total_cores&amp;gt;)return 0xd8e007fd;&lt;br /&gt;
The following code replaced the above:&lt;br /&gt;
* if(u32_processorcount &amp;gt;= &amp;lt;total_cores+1&amp;gt;)return 0xd8e007fd;&lt;br /&gt;
In theory the latter should catch everything that the former did, so it&#039;s unknown if this was really a security issue.&lt;br /&gt;
&lt;br /&gt;
The svcCreateThread changes with [[10.0.0-27|10.0.0-X]] definitely did fix a security issue.&lt;br /&gt;
* Original code: &amp;quot;if(s32_processorid &amp;gt; &amp;lt;total_cores&amp;gt;)return 0xd8e007fd;&amp;quot;&lt;br /&gt;
* New code: &amp;quot;if(s32_processorid &amp;gt;= &amp;lt;total_cores&amp;gt; || s32_processorid &amp;lt;= -4)return 0xd8e007fd;&amp;quot;&lt;br /&gt;
This fixed an off-by-one issue: if one would use processorid=total_cores, which isn&#039;t actually a valid value, svcCreateThread would accept that value on &amp;lt;[[10.0.0-27|10.0.0-X]]. This results in data being written out-of-bounds(baseaddr = arrayaddr + entrysize*processorid), which has the following result:&lt;br /&gt;
* Old3DS: Useless kernel-mode crash due to accessing unmapped memory.&lt;br /&gt;
* New3DS: uncontrolled data write into a kernel-mode L1 MMU-table. This isn&#039;t really useful: the data can&#039;t be controlled, and the data which gets overwritten is all-zero anyway(this isn&#039;t anywhere near MMU L1 entries for actually mapped memory).&lt;br /&gt;
The previous version also allowed large negative s32_processorid values(negative processorid values are special values not actual procids), but it appears using values like that won&#039;t actually do anything(meaning no crash) besides the thread not running / thread not running for a while(besides triggering a kernelpanic with certain s32_processorid value(s)).&lt;br /&gt;
| Nothing useful&lt;br /&gt;
|  [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| svcCreateThread issue: May 31, 2015. The rest: September 8, 2015, via v9.6-&amp;gt;v10.0 ARM11-kernel code-diff.&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| memchunkhax&lt;br /&gt;
| The kernel originally did not validate the data stored in the FCRAM kernel heap [[Memchunkhdr|memchunk-headers]] for free-memory at all. Exploiting this requires raw R/W access to these memchunk-headers, like physical-memory access with gspwn.&lt;br /&gt;
&lt;br /&gt;
There are &#039;&#039;multiple&#039;&#039; ways to exploit this, but the end-result for most of these is the same: overwrite code in AXIWRAM via the 0xEFF00000/0xDFF00000 kernel virtual-memory mapping.&lt;br /&gt;
&lt;br /&gt;
This was fixed in [[9.3.0-21|9.3.0-X]] by checking that the memchunk(including size, next, and prev ptrs) is located within the currently used heap memory. The kernel may also check that the next/prev ptrs are valid compared to other memchunk-headers basically. When any of these checks fail, kernelpanic() is called.&lt;br /&gt;
| When combined with other flaws: ARM11-kernelmode code execution&lt;br /&gt;
| [[9.3.0-21|9.3.0-21]]&lt;br /&gt;
| &lt;br /&gt;
| February 2014&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Multiple [[KLinkedListNode|KLinkedListNode]] SlabHeap use after free bugs&lt;br /&gt;
| The ARM11-kernel did access the &#039;key&#039; field of [[KLinkedListNode|KLinkedListNode]] objects, which are located on the SlabHeap, after freeing them. Thus, triggering an allocation of a new [[KLinkedListNode|KLinkedListNode]] object at the right time could result in a type-confusion. Pseudo-code:&lt;br /&gt;
SlabHeap_free(KLinkedListNode);&lt;br /&gt;
KObject *obj = KLinkedListNode-&amp;gt;key;  // the object there might have changed!&lt;br /&gt;
This bug appeared all over the place.&lt;br /&gt;
| ARM11-kernelmode code exec maybe&lt;br /&gt;
| [[8.0.0-18|8.0.0-18]]&lt;br /&gt;
| &lt;br /&gt;
| April 2015&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| PXI [[RPC_Command_Structure|Command]] input/output buffer permissions&lt;br /&gt;
| Originally the ARM11-kernel didn&#039;t check permissions for PXI input/output buffers for commands. Starting with [[6.0.0-11|6.0.0]] PXI input/output buffers must have RW permissions, otherwise kernelpanic is triggered.&lt;br /&gt;
| &lt;br /&gt;
| [[6.0.0-11|6.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcStartInterProcessDma]]&lt;br /&gt;
| For svcStartInterProcessDma, the kernel code had the following flaws:&lt;br /&gt;
&lt;br /&gt;
* Originally the ARM11-kernel read the input DmaConfig structure directly in kernel-mode(ldr(b/h) instructions), without checking whether the DmaConfig address is readable under userland. This was fixed by copying that structure to the SVC-mode stack, using the ldrbt instruction.&lt;br /&gt;
&lt;br /&gt;
* Integer overflows for srcaddr+size and dstaddr+size are now checked(with [[6.0.0-11]]), which were not checked before.&lt;br /&gt;
&lt;br /&gt;
* The kernel now also checks whether the srcaddr/dstaddr (+size) is within userland memory (0x20000000), the kernel now (with [[6.0.0-11]]) returns an error when the address is beyond userland memory. Using an address &amp;gt;=0x20000000 would result in the kernel reading from the process L1 MMU table, beyond the memory allocated for that MMU table(for vaddr-&amp;gt;physaddr conversion). &lt;br /&gt;
| &lt;br /&gt;
| [[6.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| DmaConfig issue: unknown. The rest: 2014&lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]] independently&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcControlMemory]] Parameter checks&lt;br /&gt;
| For svcControlMemory the parameter check had these two flaws:&lt;br /&gt;
&lt;br /&gt;
* The allowed range for addr0, addr1, size parameters depends on which MemoryOperation is being specified. The limitation for GSP heap was only checked if op=(u32)0x10003. By setting a random bit in op that has no meaning (like bit17?), op would instead be (u32)0x30003, and the range-check would be less strict and not accurate. However, the kernel doesn&#039;t actually use the input address for LINEAR memory-mapping at all besides the range-checks, so this isn&#039;t actually useful. This was fixed in the kernel by just checking for the LINEAR bit, instead of comparing the entire MemoryOperation value with 0x10003.&lt;br /&gt;
&lt;br /&gt;
* Integer overflows on (addr0+size) are now checked that previously weren&#039;t (this also applies to most other address checks elsewhere in the kernel).&lt;br /&gt;
&lt;br /&gt;
| &lt;br /&gt;
| [[5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| [[RPC_Command_Structure|Command]] request/response buffer overflow&lt;br /&gt;
| Originally the kernel did not check the word-values from the command-header. Starting with [[5.0.0-11]], the kernel will trigger a kernelpanic() when the total word-size of the entire command(including the cmd-header) is larger than 0x40-words (0x100-bytes). This allows overwriting threadlocalstorage+0x180 in the destination thread. However, since the data written there would be translate parameters (such as header-words + buffer addresses), exploiting this would likely be very difficult, if possible at all.&lt;br /&gt;
&lt;br /&gt;
If the two words at threadlocalstorage+0x180 could be overwritten with controlled data this way, one could then use a command with a buffer-header of &amp;lt;nowiki&amp;gt;((size&amp;lt;&amp;lt;14) | 2)&amp;lt;/nowiki&amp;gt; to write arbitrary memory to any RW userland memory in the destination process.&lt;br /&gt;
| &lt;br /&gt;
| [[5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| v4.1 FIRM -&amp;gt; v5.0 code diff&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|SVC stack allocation overflows]]&lt;br /&gt;
| &lt;br /&gt;
* Syscalls that allocate a variable-length array on stack, only checked bit31 before multiplying by 4/16 (when calculating how much memory to allocate). If a large integer was passed as input to one of these syscalls, an integer overflow would occur, and too little memory would have been allocated on stack resulting in a buffer overrun. &lt;br /&gt;
* The alignment (size+7)&amp;amp;~7 calculation before allocation was not checked for integer overflow.&lt;br /&gt;
&lt;br /&gt;
This might allow for ARM11 kernel code-execution.&lt;br /&gt;
&lt;br /&gt;
(Applies to svcSetResourceLimitValues, svcGetThreadList, svcGetProcessList, svcReplyAndReceive, svcWaitSynchronizationN.)&lt;br /&gt;
| &lt;br /&gt;
| [[5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| v4.1 FIRM -&amp;gt; v5.0 code diff&lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]] complementary&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcControlMemory]] MemoryOperation MAP memory-permissions&lt;br /&gt;
| svcControlMemory with MemoryOperation=MAP allows mapping the already-mapped process virtual-mem at addr1, to addr0. The lowest address permitted for addr1 is 0x00100000. Originally the ARM11 kernel didn&#039;t check memory permissions for addr1. Therefore .text as addr1 could be mapped elsewhere as RW- memory, which allowed ARM11 userland code-execution.&lt;br /&gt;
| &lt;br /&gt;
| [[4.1.0-8]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[RPC_Command_Structure|Command]] input/output buffer permissions&lt;br /&gt;
| Originally the ARM11 kernel didn&#039;t check memory permissions for the input/output buffers for commands. Starting with [[4.0.0-7]] the ARM11 kernel will trigger a kernelpanic() if the input/output buffers don&#039;t have the required memory permissions. For example, this allowed a FSUSER file-read to .text, which therefore allowed ARM11-userland code execution.&lt;br /&gt;
| &lt;br /&gt;
| [[4.0.0-7]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcReadProcessMemory/svcWriteProcessMemory memory]] permissions&lt;br /&gt;
| Originally the kernel only checked the first page(0x1000-bytes) of the src/dst buffers, for svcReadProcessMemory and svcWriteProcessMemory. There is no known retail processes which have access to these SVCs.&lt;br /&gt;
| &lt;br /&gt;
| [[4.0.0-7]]&lt;br /&gt;
| &lt;br /&gt;
| 2012?&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== [[FIRM]] Sysmodules ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[Services|&amp;quot;srv:pm&amp;quot;]] process registration&lt;br /&gt;
| Originally any process had access to the port &amp;quot;srv:pm&amp;quot;. The PID&#039;s used for the (un)registration commands are not checked either. This allowed any process to re-register itself with &amp;quot;srv:pm&amp;quot;, and therefore allowed the process to give itself access to any service, bypassing the exheader service-access-control list.&lt;br /&gt;
&lt;br /&gt;
This was fixed in [[7.0.0-13]]: starting with [[7.0.0-13]] &amp;quot;srv:pm&amp;quot; is now a service instead of a globally accessible port. Only processes with PID&#039;s less than 6 (in other words: fs, ldr, sm, pm, pxi modules) have access to it. With [[7.0.0-13]] there can only be one session for &amp;quot;srv:pm&amp;quot; open at a time(this is used by pm module), svcBreak will be executed if more sessions are opened by the processes which can access this.&lt;br /&gt;
&lt;br /&gt;
This flaw was needed for exploiting the &amp;lt;=v4.x Process9 PXI vulnerabilities from ARM11 userland ROP, since most applications don&#039;t have access to those service(s).&lt;br /&gt;
| Access to arbitrary services&lt;br /&gt;
| [[7.0.0-13]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| FSDIR null-deref&lt;br /&gt;
| [[Filesystem_services|FS]]-module may crash in some cases when handling directory reading. The trigger seems to be due to using [[FSDir:Close]] without closing the dir-handle afterwards?(Perhaps this is caused by out-of-memory?) This seems to be useless since it&#039;s just a null-deref.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| May 19(?)-20, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Useless [[SM]] off-by-one write&lt;br /&gt;
| After accepting a new session, [[SM]] writes a (handler ID (0 for srv: sessions (max. 64), 1 for the srv:pm one), pointer to session context structure in BSS) pair in a global array. However that array is only 64-entry-big instead of 65 (as it ought to be), and no bound check is done in that regard.&lt;br /&gt;
&lt;br /&gt;
Unfortunately, as of [[11.4.0-37]], the overwritten fields are totally unused after their initialization by &amp;lt;code&amp;gt;__libc_init_array&amp;lt;/code&amp;gt;.&lt;br /&gt;
| Not currently exploitable&lt;br /&gt;
| None&lt;br /&gt;
| [[11.4.0-37]]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| smpwn&lt;br /&gt;
| When registering a new service (or &amp;quot;port&amp;quot;), no bound checks are done on the service table. One can simply call RegisterPort repeatedly to overflow that table: it will overflow into the command replay structure.&lt;br /&gt;
&lt;br /&gt;
Combined with a other minor bugs in the sysmodule, it is possible to take over [[SM]] with this nevertheless difficult-to-exploit vulnerability.&lt;br /&gt;
| Code execution under [[SM]], etc.&lt;br /&gt;
| [[11.16.0-48]]&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| July 2017&lt;br /&gt;
| [[User:TuxSH|TuxSH]] (independently), presumably ichfly before &lt;br /&gt;
|-&lt;br /&gt;
| PXI cmdbuf buffer overrun &lt;br /&gt;
| Like its Arm9 counterpart, before version [[5.0.0-11|5.0.0-X]], the PXI system module did not check the command sizes. This makes it possible to get ROP under the PXI sysmodule from a pwned Process9.&lt;br /&gt;
safecerthax uses it to takeover the Arm11 processor after directly getting remote code execution on the Arm9 side. Though, is useless in classic Arm11 -&amp;gt; Arm9 chains.&lt;br /&gt;
| ROP under [[PXI_Services|PXI]]&lt;br /&gt;
| probably [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| &lt;br /&gt;
| Everyone&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Standalone Sysmodules ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in system-module system-version&lt;br /&gt;
!  Last system-module system-version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Timeframe this was added to wiki&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[CSND_Services|CSND]] sysmodule crash due to out of bounds parameters.&lt;br /&gt;
| The CSND command [[CSND:PlaySoundDirectly|PlaySoundDirectly (0x00040080)]] takes a channel ID as the first parameter. Any value outside the range [0-3] makes the system module become unstable or crash due to an out of bounds memory read. &lt;br /&gt;
| Out of bounds memory read, probably not exploitable. More research needed.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| January 2021&lt;br /&gt;
| January 22, 2021&lt;br /&gt;
| [[User:PabloMK7|PabloMK7]]&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| SSLoth: [[SSL_Services|SSL]] sysmodule improper certificate verification&lt;br /&gt;
| Initially, the SSL sysmodule missed the R_VERIFY_RES_SIGNATURE entry in the &amp;quot;resource list&amp;quot; provided to the RSA BSAFE library. Consequently, it did not check signatures when validating certificate chains. &lt;br /&gt;
| Forge fake certificates, spoof official servers and perform MitM attacks on SSL/TLS connections.&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| 2020&lt;br /&gt;
| December 18, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]], shutterbug2000 (independently)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD:ndm]] SetNZoneMacFilter (cmd8) stack smashing&lt;br /&gt;
| The length of the mac filter is not checked before being copied to a fixed-size buffer on stack.&lt;br /&gt;
| ROP under [[CECD_Services|CECD]] sysmodule&lt;br /&gt;
| None&lt;br /&gt;
| [[11.13.0-45]]&lt;br /&gt;
| 2020&lt;br /&gt;
| July 20, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] message box access &lt;br /&gt;
| CECD allows any process to write to any message box, thus allowing to write Streetpass data to the message box of any title.&lt;br /&gt;
| Install exploit for any title having a vulnerability in Streetpass data parsers (see CTRSDK Streetpass parser vulnerability).&lt;br /&gt;
| None&lt;br /&gt;
| None&lt;br /&gt;
| ?&lt;br /&gt;
| June 1, 2020&lt;br /&gt;
| Everyone?&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] packet type 0x32/0x34 stack-smashing&lt;br /&gt;
| When parsing Streetpass packets of type 0x32 and 0x34, CECD copies a list without checking the number of entries. The packet length is limited to 0x400 bytes, which is not enough to reach the end of the stack frame and overwrite the return address. However, the buffer located just next to the packet buffer is actually filled with data sent just before, hence actually allowing to overwrite the whole stack frame with conrolled data.&lt;br /&gt;
| RCE under [[CECD_Services|CECD]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| Summer 2019&lt;br /&gt;
| June 1, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] TMP files parser multiple vulnerabilities&lt;br /&gt;
| When parsing &amp;quot;TMP_XXX&amp;quot; files, CECD does not check the number of messages contained in the file. This allows to overflow the array of message pointers and message sizes on the stack. Pointers aren&#039;t controlled and sizes are limited (one cannot send gigabytes of data...), yet the last message size can be an arbitrary value (the current message pointer goes outside the file buffer and the parsing loop is broken). This allows to overwrite a pointer to a lock object on the stack and decrement an arbitrary value in memory. One can change the TMP file parsing mode to have CECD trying to free all the message buffers after parsing the next TMP file. The parsing mode is usually restored when parsing a new TMP file, but an invalid TMP file allows to make a function returns an error before the mode is restored , the return value is not checked and the parser consider the file valid. The message pointers and sizes arrays are not updated though, this is not a problem since the previous TMP file buffer is reused for the new TMP file in memory. Thus the message pointers actually points to controlled data. This allows to get a bunch of fake heap chunk freed, thus a bunch of unsafe unlink arbitrary writes.&lt;br /&gt;
| RCE under [[CECD_Services|CECD]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| Summer 2019&lt;br /&gt;
| June 1, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Config_Services|CFG]]:CreateConfigInfoBlk integer underflow&lt;br /&gt;
| When creating a new block it checks the size of the block is &amp;lt;= 0x8000, but it doesn&#039;t check that the block size is less than the remaining space. This induces an integer underflow (remaining_space-block_size), the result is then used for another check (buf_start+current_offset+constant &amp;lt;= remaining_space-block_size) and then in a mempcy call (dest = buf_start+(u16)(remaining_space-block_size), size =block_size). This allow for writing past the buffer, however because of the u16 cast in the memcpy call memory has to be mapped from buf_start to buf_start+0x10000 (cannot write backward).&lt;br /&gt;
| Theoritically ROP under CFG services, but BSS section is to small (size &amp;lt;= 0x10000) so it only results in a crash.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.8.0-41]]&lt;br /&gt;
| November, 2018&lt;br /&gt;
| November 24, 2018&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[MP:SendDataFrame]] missing input array index validation&lt;br /&gt;
| [[MP:SendDataFrame]] doesn&#039;t validate the input index at cmdreq[1], unless the function for flag=non-zero is executed. This is used to calculate the following, without validating the index at all: someptr = stateptr + (index*0x924) + somestateoffset.&lt;br /&gt;
&lt;br /&gt;
After validating some flags from someptr, when input_flag=0 the input buffer data is copied to someptr+someotheroffset+0x14 with the u16 size loaded from someptr+someotheroffset.&lt;br /&gt;
&lt;br /&gt;
With a large input index someptr could be setup to be at a &amp;lt;target address&amp;gt;, for overwriting memory.&lt;br /&gt;
&lt;br /&gt;
This is probably difficult to exploit.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[8.0.0-18]](MP-sysmodule v2048)&lt;br /&gt;
| January 22, 2017&lt;br /&gt;
| January 22, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[MP_Services|MP]] cmd1 out-of-bounds handle read&lt;br /&gt;
| MP-sysmodule handles the input parameter for cmd1 as a s32. It checks for &amp;gt;=16, but not &amp;lt;0. With &amp;lt;16 it basically does the following(array of entries 4-bytes each): *outhandle = ((Handle*)(stateptr+offsetinstate))[inputindex].&lt;br /&gt;
&lt;br /&gt;
Hence, this can be used to load any handle in MP-sysmodule memory. MP doesn&#039;t really have any service handles of interest however(can be obtained from elsewhere too).&lt;br /&gt;
| Reading any handle in MP-sysmodule memory.&lt;br /&gt;
| None&lt;br /&gt;
| [[8.0.0-18]](MP-sysmodule v2048)&lt;br /&gt;
| January 21, 2017&lt;br /&gt;
| January 22, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| AM stack/.bss infoleak via [[AM:ReadTwlBackupInfo]]([[AM:ReadTwlBackupInfoEx|Ex]])&lt;br /&gt;
| After writing the output-info structure to stack, it then copies that structure to the output buffer ptr using the size from the command. The size is not checked. This could be used to read data from the AM-service-thread stack handling the command + .bss.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;This was not tested on hardware.&#039;&#039;&#039;&lt;br /&gt;
| Stack/.bss reading&lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27]](AM v9217)&lt;br /&gt;
| Roughly October 17, 2016&lt;br /&gt;
| October 25, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| AM module APcert infoleak via 00000000.ctx files&lt;br /&gt;
| Just after a download title is purchased from the eShop, the .ctx is in an initialized state of all FFs past the header. During download, the FF area is filled with the console APcert. Thus, it is possible to create a xorpad from the initial state and use it to decrypt the APcert filled state.&lt;br /&gt;
| APcert contains the deviceID, which can beneficial in decrypting the movable.sed (since deviceID is mathmatically related to the LFCS).&lt;br /&gt;
| None&lt;br /&gt;
| [[11.16.0-49]]&lt;br /&gt;
| August, 2022&lt;br /&gt;
| March 17, 2023&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| [[MVD_Services|MVD]]: Stack buffer overflow with [[MVDSTD:SetupOutputBuffers]].&lt;br /&gt;
| The input total_entries is not validated when initially processing the input entry-list. This fixed-size input entry-list is copied to stack from the command request. The loop for processing this initializes a global table, the converted linearmem-&amp;gt;physaddrs used there are also copied to stack(0x8-bytes of physaddrs per entry).&lt;br /&gt;
&lt;br /&gt;
If total_entries is too large, MVD-sysmodule will crash due to reading unmapped memory following the stack(0x10000000). Afterwards if the out-of-bounds total_entries is smaller than that, it will crash due accessing address 0x0, hence this useless.&lt;br /&gt;
| MVD-sysmodule crash.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.0.0-20]]&lt;br /&gt;
| April 22, 2016 (Tested on the 25th)&lt;br /&gt;
| April 25, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWM_Services|NWM]]: Using CTRSDK heap with UDS sharedmem from the user-process.&lt;br /&gt;
| See the HTTP-sysmodule section below.&lt;br /&gt;
&lt;br /&gt;
CTRSDK heap is used with the sharedmem from [[NWMUDS:InitializeWithVersion]]. Buffers are allocated/freed under this heap using [[NWMUDS:Bind]] and [[NWMUDS:Unbind]].&lt;br /&gt;
&lt;br /&gt;
Hence, overwriting sharedmem with gspwn then using [[NWMUDS:Unbind]] results in the usual controlled CTRSDK memchunk-header write, similar to HTTP-sysmodule.&lt;br /&gt;
&lt;br /&gt;
This could be done by creating an UDS network, without any other nodes on the network.&lt;br /&gt;
&lt;br /&gt;
Besides CTRSDK memchunk-headers, there are no addresses stored under this sharedmem.&lt;br /&gt;
| ROP under NWM-module.&lt;br /&gt;
| None (need to check, but CTRSDK heap code is vulnerable)&lt;br /&gt;
| [[9.0.0-20|9.0.0-X]]&lt;br /&gt;
| April 10, 2016&lt;br /&gt;
| April 16, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[DLP_Services|DLP]]: Out-of-bounds memory access during spectator [[Download_Play|data-frame]] checksum calculation&lt;br /&gt;
| DLP doesn&#039;t validate the frame_size when receiving spectator data-frames at all, unlike non-spectator data-frames. The actual spectator data-frame parsing code doesn&#039;t use that field either. However, the data-frame checksum calculation code called during checksum verification does use the frame_size for loading the size of the framebuf.&lt;br /&gt;
&lt;br /&gt;
Hence, using a large frame_size like 0xFFFF will result in the checksum calculation code reading data out-of-bounds. This isn&#039;t really useful, you could trigger a remote local-WLAN DLP-sysmodule crash while a 3DS system is scanning for DLP networks(due to accessing unmapped memory), but that&#039;s about all(trying to infoleak with this likely isn&#039;t useful either).&lt;br /&gt;
| DLP-sysmodule crash, handled by dlplay system-application by a &amp;quot;connection interrupted&amp;quot; error eventually then a fatal-error via ErrDisp.&lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| April 8, 2016 (Tested on the 10th)&lt;br /&gt;
| April 10, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[DLP_Services|DLP]]: Out-of-bounds output data writing during spectator sysupdate titlelist [[Download_Play|data-frame]] handling&lt;br /&gt;
| The total_entries and out_entryindex fields for the titlelist DLP spectator data-frames are not validated. This is parsed during DLP network scanning. Hence, the specified titlelist data can be written out-of-bounds using the specified out_entryindex and total_entries. A crash will occur while reading the input data-frame titlelist if total_entries is larger than 0x27A, due to accessing unmapped memory.&lt;br /&gt;
&lt;br /&gt;
There&#039;s not much non-zero data to overwrite following the output buffer(located in sharedmem), any ptrs are located in sharedmem. Overwriting certain ptr(s) are only known to cause a crash when attempting to use the DLP-client shutdown service-command.&lt;br /&gt;
&lt;br /&gt;
There&#039;s no known way to exploit the above crash, since the linked-list code involves writes zeros(with a controlled start ptr).&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| April 8-9, 2016&lt;br /&gt;
| April 10, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[IR_Services|IR]]: Stack buffer overflow with custom hardware&lt;br /&gt;
| Originally IR sysmodule used the read value from the I2C-IR registers TXLVL and RXLVL without validating them at all. See [[10.6.0-31|here]] for the fix. This is the size used for reading the data-recv FIFO, etc. The output buffer for reading is located on the stack.&lt;br /&gt;
&lt;br /&gt;
This should be exploitable if one could successfully setup the custom hardware for this and if the entire intended sizes actually get read from I2C.&lt;br /&gt;
| ROP under IR sysmodule.&lt;br /&gt;
| [[10.6.0-31|10.6.0-31]]&lt;br /&gt;
| &lt;br /&gt;
| February 23, 2016 (Unknown if it was noticed before then)&lt;br /&gt;
| February 23, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[HTTP_Services|HTTP]]: Using CTRSDK heap with sharedmem from the user-process.&lt;br /&gt;
| The data from httpcAddPostDataAscii and other commands is stored under a CTRSDK heap. That heap is the sharedmem specified by the user-process via the HTTPC Initialize command.&lt;br /&gt;
Normally this sharedmem isn&#039;t accessible to the user-process once the sysmodule maps it, hence using it is supposed to be &amp;quot;safe&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This isn&#039;t the case due to gspwn however. Since CTRSDK heap code is so insecure in general, one can use gspwn to locate the HTTPC sharedmem + read/write it, then trigger a mem-write under the sysmodule. This can then be used to get ROP going under HTTP-sysmodule.&lt;br /&gt;
&lt;br /&gt;
This is exploited by [https://github.com/yellows8/ctr-httpwn/ctr-httpwn ctr-httpwn].&lt;br /&gt;
| ROP under HTTP sysmdule.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.13.0-45|11.13.0-X]]&lt;br /&gt;
| Late 2015&lt;br /&gt;
| March 22, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NIM_Services|NIM]]: Downloading old title-versions from eShop&lt;br /&gt;
| Multiple NIM service commands(such as [[NIMS:StartDownload]]) use a title-version value specified by the user-process, NIM does not validate that this input version matches the latest version available via SOAP. Therefore, when combined with AM(PXI) [[#Process9|title-downgrading]] via deleting the target eShop title with System Settings Data Management(if the title was already installed), this allows downloading+installing any title-version from eShop &#039;&#039;if&#039;&#039; it&#039;s still available from CDN.&lt;br /&gt;
The easiest way to exploit this is to just patch the eShop system-application code using these NIM commands(ideally the code which loads the title-version).&lt;br /&gt;
&lt;br /&gt;
Originally this was tested with a debugging-system via modded-FIRM, eventually smea implemented it in HANS for the 32c3 release.&lt;br /&gt;
| Downloading old title-versions from eShop&lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| October 24, 2015 (Unknown when exactly the first eShop title downgrade was actually tested, maybe November)&lt;br /&gt;
| January 7, 2016 (Same day Ironfall v1.0 was removed from CDN via the main-CXI files)&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SPI_Services|SPI]] service out-of-bounds write&lt;br /&gt;
| cmd1 has out-of-bounds write allowing overwrite of some static variables in .data.&lt;br /&gt;
| Code execution under spi sysmodule; access to [[CONFIG11_Registers|CFG11_GPUPROT]] and ultimately kernel code execution. &lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NFC_Services|NFC]] module service command buf-overflows&lt;br /&gt;
| NFC module copies data with certain commands, from command input buffers to stack without checking the size. These commands include the following, it&#039;s unknown if there&#039;s more commands with similar issues: &amp;quot;nfc:dev&amp;quot; &amp;lt;0x000C....&amp;gt; and &amp;quot;nfc:s&amp;quot; &amp;lt;0x0037....&amp;gt;.&lt;br /&gt;
Since both of these commands are stubbed in the Old3DS NFC module from the very first version(those just return an error), these issues only affect the New3DS NFC module.&lt;br /&gt;
&lt;br /&gt;
There&#039;s no known retail titles which have access to either of these services.&lt;br /&gt;
| ROP under NFC module.&lt;br /&gt;
| New3DS: None&lt;br /&gt;
| New3DS: [[9.5.0-22]]&lt;br /&gt;
| December 2014?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[News_Services|NEWSS]] service command notificationID validation failure&lt;br /&gt;
| This module does not validate the input notificationID for &amp;lt;nowiki&amp;gt;&amp;quot;news:s&amp;quot;&amp;lt;/nowiki&amp;gt; service commands. This is an out-of-bounds array index bug. For example, [[NEWSS:SetNotificationHeader]] could be used to exploit news module: this copies the input data(size is properly checked) to: out = newsdb_savedata+0x10 + (someu32array[notificationID]*0x70).&lt;br /&gt;
| ROP under news module.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.7.0-25|9.7.0-X]]&lt;br /&gt;
| December 2014&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWMUDS:DecryptBeaconData]] heap buffer overflow&lt;br /&gt;
| input_size = 0x1E * &amp;lt;value the u8 from input_[[NWM_Services|networkstruct]]+0x1D&amp;gt;. Then input_tag0 is copied to a heap buffer. When input_size is larger than 0xFA-bytes, it will then copy input_tag1 to &amp;lt;end_address_of_previous_outbuf&amp;gt;, with size=input_size-0xFA.&lt;br /&gt;
&lt;br /&gt;
This can be triggered by either using this command directly, or by boadcasting a wifi beacon which triggers it while a 3DS system running the target process is in range, when the process is scanning for hosts to connect to. Processes will only pass tag data to this command when the wlancommID and other thing(s) match the values for the process.&lt;br /&gt;
&lt;br /&gt;
There&#039;s no known way to actually exploit this for getting ROP under NWM-module, at the time of originally adding this to the wiki. This is because the data which gets copied out-of-bounds *and* actually causes crash(es), can&#039;t be controlled it seems(with just broadcasting a beacon at least). It&#039;s unknown whether this could be exploited from just using NWMUDS service-cmd(s) directly.&lt;br /&gt;
| Without any actual way to exploit this: NWM-module DoS, resulting in process termination(process crash). This breaks *everything* involving wifi comms, a reboot is required to recover from this.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.0.0-20]]&lt;br /&gt;
| ~September 23, 2014(see the [[NWMUDS:DecryptBeaconData]] page history)&lt;br /&gt;
| August 3, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[HID_Services|HID]] module shared-mem&lt;br /&gt;
| HID module does not validate the index values in [[HID_Shared_Memory|sharedmem]](just changes index to 0 when index == maxval when updating), therefore large values will result in HID module writing HID data to arbitrary addresses.&lt;br /&gt;
| ROP under HID module, but this is *very* unlikely to be exploitable since the data written is HID data.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.3.0-21]]&lt;br /&gt;
| 2014?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| gspwn&lt;br /&gt;
| GSP module does not validate addresses given to the GPU. This allows a user-mode application/applet to read/write to a large part of physical FCRAM using GPU DMA. From this, you can overwrite the .text segment of the application you&#039;re running under, and gain real code-execution from a ROP-chain. Normally applets&#039; .text([[Home Menu]], [[Internet Browser]], etc) is located beyond the area accessible by the GPU, except for [[RO_Services|CROs]] used by applets([[Internet Browser]] for example).&lt;br /&gt;
&lt;br /&gt;
FCRAM is gpu-accessible up to physaddr 0x26800000 on Old3DS, and 0x2D800000 on New3DS. This is BASE_memregion_start(aka SYSTEM_memregion_end)-0x400000 (0x800000 with New3DS) with the default memory-layout on Old3DS/New3DS. With [[11.3.0-36|11.3.0-X]] the cutoff now varies due to the new [[SVC]] 0x59. The New3DS &amp;quot;normal&amp;quot;(non-APPLICATION) cutoff was changed to 0x2D000000 due to the new [[SVC]] 0x59.&lt;br /&gt;
| User-mode code execution.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| Early 2014&lt;br /&gt;
| &lt;br /&gt;
| smea, [[User:Yellows8|Yellows8]]/others before then&lt;br /&gt;
|-&lt;br /&gt;
| [[GSP_Services|GSP]]: client management failures&lt;br /&gt;
| Shared memory of GSP clients is all on the same page, this allows any GSP client to craft custom GX commands for other clients. Additionally, [[GSPGPU:TriggerCmdReqQueue]] does not check if the calling client has rendering rights.&lt;br /&gt;
&lt;br /&gt;
These two flaws can be used to craft DMA/Transfer Engine commands within a different GSP client to issue reads/writes to both physical (akin to gspwn) and virtual memory of said client.&lt;br /&gt;
| Arbitrary RW from and into a client process.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.17.0-50|11.17.0-50]]&lt;br /&gt;
| May 2025&lt;br /&gt;
| May 2025&lt;br /&gt;
| [[User:kynex7510|kynex7510]], probably others&lt;br /&gt;
|-&lt;br /&gt;
| [[GSP_Services|GSP]]: unbound DMA&lt;br /&gt;
| GSP doesn&#039;t really care what process handle is passed to [[GSPGPU:AcquireRight]]. Hence, it&#039;s possible to craft DMA commands to read/write within that process virtual address space.&lt;br /&gt;
| Arbitrary RW within any process.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.17.0-50|11.17.0-50]]&lt;br /&gt;
| June 2025&lt;br /&gt;
| June 2025&lt;br /&gt;
| [[User:kynex7510|kynex7510]]&lt;br /&gt;
|-&lt;br /&gt;
| rohax&lt;br /&gt;
| Using gspwn, it is possible to overwrite a loaded [[CRO0]]/[[CRR0]] after its RSA-signature has been validated. Badly validated [[CRO0]] header leads to arbitrary read/write of memory in the ro-process. This gives code-execution in the ro module, who has access to [[SVC|syscalls]] 0x70-0x72, 0x7D.&lt;br /&gt;
&lt;br /&gt;
This was fixed after [[ninjhax]] release by adding checks on [[CRO0]]-based pointers before writing to them.&lt;br /&gt;
| Memory-mapping syscalls.&lt;br /&gt;
| [[9.3.0-21]]&lt;br /&gt;
| [[9.4.0-21]]&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
| smea, [[User:Plutooo|plutoo]] joint effort&lt;br /&gt;
|-&lt;br /&gt;
| [[RO_Services|RO]]: custom CRO mapping into any process&lt;br /&gt;
| RO stores pointers to previously loaded CRRs in internal state, however it doesn&#039;t keep track to which process they belong to. Thus pointers can be reused among different processes, and since CRR verification only happens on load this bypasses it.&lt;br /&gt;
&lt;br /&gt;
Granted a handle to the target process is available, the following strategy can be used to load a custom CRO into any process:&lt;br /&gt;
&lt;br /&gt;
* Map valid CRS, CRR into the current application, and initialize RO normally;&lt;br /&gt;
* Write custom CRR, CRO into the target process at the same addresses (process handle can be used with GSP DMA capabilities for read/write operations, see above);&lt;br /&gt;
* Load CRO into the target process using its handle.&lt;br /&gt;
&lt;br /&gt;
| Code execution in the target process.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.17.0-50|11.17.0-50]]&lt;br /&gt;
| June 2025&lt;br /&gt;
| June 2025&lt;br /&gt;
| [[User:kynex7510|kynex7510]]&lt;br /&gt;
|-&lt;br /&gt;
| [[RO_Services|RO]]: target process patching&lt;br /&gt;
| A custom CRS file can be used to apply relocation patches anywhere in the target process, and since all pages are mapped as RW, this includes executable pages aswell.&lt;br /&gt;
&lt;br /&gt;
A possible exploitation strategy takes advantage of relocation type 2 (R_ARM_ABS32), where the payload data is encoded as a series of import patches, each encoding 4 bytes of data in the &amp;quot;addend&amp;quot; field.&lt;br /&gt;
| Arbitrary write in the target process bypassing page protections, hence code execution.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.17.0-50|11.17.0-50]]&lt;br /&gt;
| June 2025&lt;br /&gt;
| July 2025&lt;br /&gt;
| [[User:kynex7510|kynex7510]]&lt;br /&gt;
|-&lt;br /&gt;
| Region free&lt;br /&gt;
| Only [[Home Menu]] itself checks gamecards&#039; region when launching them. Therefore, any application launch that is done directly with [[NS]] without signaling Home Menu to launch the app, will result in region checks being bypassed.&lt;br /&gt;
This essentially means launching the gamecard with the [[NS_and_APT_Services|&amp;quot;ns:s&amp;quot;]] service. The main way to exploit this is to trigger a FIRM launch with an application specified, either with a normal FIRM launch or a hardware [[NSS:RebootSystem|reboot]].&lt;br /&gt;
| Launching gamecards from any region + bypassing Home Menu gamecard-sysupdate installation&lt;br /&gt;
| None&lt;br /&gt;
| Last tested with [[10.1.0-27|10.1.0-X]].&lt;br /&gt;
| June(?) 2014&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWM_Services|NWM]] service-cmd state null-ptr deref&lt;br /&gt;
| The NWMUDS service command code loads a ptr from .data, adds an offset to that, then passes that as the state address for the actual command-handler function. The value of the ptr loaded from .data is not checked, therefore this will cause crashes due to that being 0x0 when NWMUDS was not properly initialized.&lt;br /&gt;
It&#039;s unknown whether any NWM services besides NWMUDS have this issue.&lt;br /&gt;
| This is rather useless since it&#039;s only a crash caused by a state ptr based at 0x0.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.0.0-20]]&lt;br /&gt;
| 2013?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== General/CTRSDK ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in version&lt;br /&gt;
!  Last version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] Streetpass message exheader stack-smashing&lt;br /&gt;
| When parsing streetpass messages, &amp;quot;nn::cec::CTR::Message::InputMessage&amp;quot; calls &amp;quot;nn::cec::CTR::Message::SetExHeaderWithoutCalc&amp;quot; for each exheader entry in the input message. The number of entries should not exceed 16 but remains unchecked, leading to a stack-buffer-overflow.&lt;br /&gt;
| ROP under any application parsing Streetpass messages&lt;br /&gt;
Remote code execution under [[CECD_Services|CECD]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| &lt;br /&gt;
| 2019&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWM_Services|UDS]] beacon additional-data buffer overflow&lt;br /&gt;
| Originally CTRSDK did not validate the UDS additional-data size before using that size to copy the additional-data to a [[NWM_Services|networkstruct]]. This was eventually fixed.&lt;br /&gt;
This was discovered while doing code RE with an old dlp-module version. It&#039;s unknown in what specific CTRSDK version this was fixed, or even what system-version updated titles with a fixed version.&lt;br /&gt;
&lt;br /&gt;
It&#039;s unknown if there&#039;s any titles using a vulnerable CTRSDK version which are also exploitable with this(dlp module can&#039;t be exploited with this).&lt;br /&gt;
&lt;br /&gt;
The maximum number of bytes that can be written beyond the end of the outbuf is 0x37-bytes, with additionaldata_size=0xFF.&lt;br /&gt;
| Perhaps ROP, very difficult if possible with anything at all&lt;br /&gt;
| ?&lt;br /&gt;
| &lt;br /&gt;
| September(?) 2014&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| CTPK buffer overflow&lt;br /&gt;
| At offset 0x20 in CTPK is an array for each texture, each entry is 0x20-bytes. This contains a wordindex(entry+0x18) for some srcdata relative to CTPK+0, and an u8 wordsize(entry+0x14) for this data. The CTRSDK function handling this doesn&#039;t validate the size, when copying srcdata using this size to the output buffer. Applications usually have the output buffer on the stack, hence stack buffer overflow.&lt;br /&gt;
&lt;br /&gt;
While CTPK(*.ctpk) are normally only loaded from RomFS, some application(s) load from elsewhere too.&lt;br /&gt;
| ROP under the target application.&lt;br /&gt;
| None?&lt;br /&gt;
| &amp;quot;[SDK+NINTENDO:CTR_SDK-11_4_0_200_none]&amp;quot;&lt;br /&gt;
| November 14, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Pia vulns&lt;br /&gt;
| [https://switchbrew.org/wiki/Switch_System_Flaws#Pia Originally discovered in Pia v5.x for Switch], these vulns are also present in earlier versions (v3.x/4.x/5.x, possibly earlier?) for 3DS (and Wii U too).&lt;br /&gt;
Pia encryption generally wasn&#039;t used pre-Switch (sent packets are plaintext). 3DS is affected by all Pia vulns listed above except for LAN. The functionality for ParseLeaveMeshInvitation doesn&#039;t exist in 3DS Pia v3.9.2. Wii U is affected by all listed Pia vulns except for the LAN vulns.&lt;br /&gt;
| See [https://switchbrew.org/wiki/Switch_System_Flaws#Pia here].&lt;br /&gt;
| Unfixed on 3DS/Wii U&lt;br /&gt;
| &amp;quot;[SDK+Nintendo:PIA_5_4_3]&amp;quot;&lt;br /&gt;
| See [https://switchbrew.org/wiki/Switch_System_Flaws#Pia here]; separately checked later (UpdateConnectionReport) by [[User:Riley|Riley]] on: June 14, 2023&lt;br /&gt;
| [[User:Yellows8|Yellows8]]; added to 3dbrew (UpdateConnectionReport) by [[User:Riley|Riley]] later&lt;br /&gt;
|-&lt;br /&gt;
| pialease nerf: stack overflow in Pia when parsing UDS packet cmd=5 &amp;quot;UpdateMigrationNodeInfoMessage&amp;quot;&lt;br /&gt;
| A UDS packet as received by Pia contains a command type, where cmd=1 is higher-layer game-data, and other cmds are parsed internally.&lt;br /&gt;
&lt;br /&gt;
A function named &amp;quot;UdsNode::ParseUpdateMigrationNodeInfoMessage&amp;quot; is called to handle packets with cmd=5.&lt;br /&gt;
&lt;br /&gt;
This checks the player nodeID (returns if not player 1, that is, UDS network host), then calls an additional function which does a loop of 64-bit copies to a fixed-size stack buffer using unchecked index and data from the received packet contents.&lt;br /&gt;
&lt;br /&gt;
This therefore leads to trivial RCE (of every UDS network client) by just sending a single UDS packet; only 0xC u64s on stack can be overwritten easily, but just 2 is enough to start a ROP chain and pivot to the rest of the UDS packet contents elsewhere on the stack.&lt;br /&gt;
&lt;br /&gt;
To exploit some games, an attacker would need to also reimplement the DLP server protocol (and any quirks that game has when parsing the UDS network passphrase obtained from the DLP server). One game that requires this is Mario Party: Island Tour (only the dlplay child connects to a UDS network).&lt;br /&gt;
&lt;br /&gt;
Earliest version of Pia known to be vulnerable is v2.x. v1.x still parses this packet, but does not have the stack-copy loop (index is still unchecked there leading to heap overflow but due to overwrites not being contiguous in memory it may or may not be exploitable).&lt;br /&gt;
&lt;br /&gt;
Fixed with Pia version 4.x, which refactored the UDS send/receive wrapper code and parses completely different commands.&lt;br /&gt;
| ROP under the vulnerable application. A server can exploit every client connected to it; a client can exploit every other client connected to that server.&lt;br /&gt;
| &amp;quot;[SDK+Nintendo:PIA_4_2_0]&amp;quot;&lt;br /&gt;
| &amp;quot;[SDK+Nintendo:PIA_3_10_2]&amp;quot;, &amp;quot;[SDK+Nintendo:PIA_4_2_0]&amp;quot;&lt;br /&gt;
| Discovery: June 3, 2023.&lt;br /&gt;
&lt;br /&gt;
Wiki: November 20, 2023.&lt;br /&gt;
| [[User:Riley|Riley]]&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=CRO0&amp;diff=23622</id>
		<title>CRO0</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=CRO0&amp;diff=23622"/>
		<updated>2025-07-23T20:19:20Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Relocation code&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:File formats]]&lt;br /&gt;
&lt;br /&gt;
CRO with extension .cro is used for &amp;quot;DLLs&amp;quot;. CRS with extension .crs is in the same format of CRO but storing the symbol information of the static module (the main application). The end of the file is aligned to a 0x1000-byte boundary with 0xCC bytes.&lt;br /&gt;
&lt;br /&gt;
The first hash-table entry hashes the 0x100-byte header following the hash-table. The following hash-table entries hash the sections specified in the header.&lt;br /&gt;
&lt;br /&gt;
When the RO module loads the entire CRO into process memory(mapped in the 0x00100000-0x04000000 region), it modifies the mapped CRO data. The magic field is also changed to &amp;quot;FIXD&amp;quot; if fix level is not 0.&lt;br /&gt;
&lt;br /&gt;
Upon loading, the RO module will look for export symbol &amp;quot;nnroAeabiAtexit_&amp;quot; to patch it to its import symbol &amp;quot;__aeabi_atexit&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
For dumping symbols and loading a CRO into IDA, see [https://github.com/plutooo/ctr/] and [https://github.com/wwylele/IDA_plugin_CRO].&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x80&lt;br /&gt;
| SHA-256 hash-table, verified by [[CRR0|CRR]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| 0x04&lt;br /&gt;
| Magic &amp;quot;CRO0&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| 0x04&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x88&lt;br /&gt;
| 0x04&lt;br /&gt;
| Next loaded CRO pointer, set by RO during loading (Usually zero when the CRO is being loaded)&lt;br /&gt;
|-&lt;br /&gt;
| 0x8C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Previous loaded CRO pointer, set by RO during loading&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| 0x04&lt;br /&gt;
| File size&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| 0x04&lt;br /&gt;
| .bss size&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| 0x04&lt;br /&gt;
| Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 0x9C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 0xA0&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] that is always the same as export symbol &amp;quot;nnroControlObject_&amp;quot;. 0xFFFFFFFF in CRS&lt;br /&gt;
|-&lt;br /&gt;
| 0xA4&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for &amp;quot;OnLoad&amp;quot; function, which will be called when the module is initialized. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xA8&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for &amp;quot;OnExit&amp;quot; function, which will be called when the module is finalized. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xAC&lt;br /&gt;
| 0x04&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for &amp;quot;OnUnresolved&amp;quot; function, which will be called when an unresolved function is called. Set to 0xFFFFFFFF if not exists.&lt;br /&gt;
|-&lt;br /&gt;
| 0xB0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Code offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xB4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Code size&lt;br /&gt;
|-&lt;br /&gt;
| 0xB8&lt;br /&gt;
| 0x04&lt;br /&gt;
| .data offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xBC&lt;br /&gt;
| 0x04&lt;br /&gt;
| .data size&lt;br /&gt;
|-&lt;br /&gt;
| 0xC0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Module Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xC4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Module Name size&lt;br /&gt;
|-&lt;br /&gt;
| 0xC8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Segment Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xCC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Segment Table num (size = num*12)&lt;br /&gt;
|-&lt;br /&gt;
| 0xD0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Export Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xD4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Export Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0xD8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Export Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xDC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Export Table num (size = num * 4)&lt;br /&gt;
|-&lt;br /&gt;
| 0xE0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Strings offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xE4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Strings size&lt;br /&gt;
|-&lt;br /&gt;
| 0xE8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Tree offset (fast lookups based on a trie-like structure)&lt;br /&gt;
|-&lt;br /&gt;
| 0xEC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Export Tree num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0xF0&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Module Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xF4&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Module Table num (size = num * 20)&lt;br /&gt;
|-&lt;br /&gt;
| 0xF8&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Patches offset&lt;br /&gt;
|-&lt;br /&gt;
| 0xFC&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Patches num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0x100&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x104&lt;br /&gt;
| 0x04&lt;br /&gt;
| Named Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x108&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x10C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Indexed Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x110&lt;br /&gt;
| 0x04&lt;br /&gt;
| Anonymous Import Table offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x114&lt;br /&gt;
| 0x04&lt;br /&gt;
| Anonymous Import Table num (size = num * 8)&lt;br /&gt;
|-&lt;br /&gt;
| 0x118&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Strings offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x11C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Import Strings size&lt;br /&gt;
|-&lt;br /&gt;
| 0x120&lt;br /&gt;
| 0x04&lt;br /&gt;
| unk8 offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x124&lt;br /&gt;
| 0x04&lt;br /&gt;
| unk8 num&lt;br /&gt;
|-&lt;br /&gt;
| 0x128&lt;br /&gt;
| 0x04&lt;br /&gt;
| Relocation Patches offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x12C&lt;br /&gt;
| 0x04&lt;br /&gt;
| Relocation Patches num (size = num * 12)&lt;br /&gt;
|-&lt;br /&gt;
| 0x130&lt;br /&gt;
| 0x04&lt;br /&gt;
| unk9 offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x134&lt;br /&gt;
| 0x04&lt;br /&gt;
| unk9 num&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Segment offset (4 bytes)==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bits&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-3&lt;br /&gt;
| Segment index for table&lt;br /&gt;
|-&lt;br /&gt;
| 4-31&lt;br /&gt;
| Offset into segment&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Segment Table entry (12 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment offset &lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment size&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| Segment id (0 = .text, 1 = .rodata, 2 = .data, 3 = .bss)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Named Export Table entry (8 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for export&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Indexed Export Table entry (4 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for export&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Named Import Table entry (8 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Indexed Import Table entry (8 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| index of the export symbol&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Anonymous Import Table entry (8 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] of the export symbol&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a linear list that contains the patches for this import&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Import Module Table entry (20 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Module name offset&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Indexed import num&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a sub list in Indexed Import Table&lt;br /&gt;
|-&lt;br /&gt;
| 0xC&lt;br /&gt;
| 0x4&lt;br /&gt;
| Anonymous import num&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| 0x4&lt;br /&gt;
| Offset of the head of a sub list in Anonymous Import Table&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Patch entry (12 bytes)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| [[#Segment offset (4 bytes)|Segment offset]] for output.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x1&lt;br /&gt;
| Patch type (R_ARM_NONE = 0, R_ARM_ABS32 = 2, R_ARM_REL32 = 3, R_ARM_THM_PC22 = 10, R_ARM_CALL = 28, R_ARM_JUMP24 = 29, R_ARM_TARGET1 = 38, R_ARM_PREL31 = 42)&lt;br /&gt;
|-&lt;br /&gt;
| 0x5&lt;br /&gt;
| 0x1&lt;br /&gt;
| For import patches, non-zero if last entry; for relocation patches, this is the referred segment index&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x1&lt;br /&gt;
| For import patches, 1 is written to first entry if all symbols loaded successfully; unknown (padding?) for relocation patches&lt;br /&gt;
|-&lt;br /&gt;
| 0x7&lt;br /&gt;
| 0x1&lt;br /&gt;
| Unknown (padding?)&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| addend&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Relocation code from RO:&lt;br /&gt;
&lt;br /&gt;
 static Result writePatch(u32* out, u32 patchType, u32 addend, u32 base, u32 inputPtr) {&lt;br /&gt;
     const s32 branchOffset = inputPtr - base;&lt;br /&gt;
     u32 offset = base + addend - inputPtr;&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_NONE)&lt;br /&gt;
         return 0;&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_ABS32 || patchType == R_ARM_TARGET1) {&lt;br /&gt;
         *out = base + addend;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_REL32) {&lt;br /&gt;
         *out = offset;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_THM_PC22) {&lt;br /&gt;
         // +-4MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x400000 || branchOffset &amp;lt;= -0x400000)&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         if (base &amp;amp; 1) {&lt;br /&gt;
             *out = (((offset &amp;gt;&amp;gt; 12) | 0xF000) &amp;lt;&amp;lt; 16) | ((offset &amp;lt;&amp;lt; 4) &amp;gt;&amp;gt; 5) | 0xF800;&lt;br /&gt;
         } else {&lt;br /&gt;
             if (offset &amp;amp; 2)&lt;br /&gt;
                 offset += 2;&lt;br /&gt;
 &lt;br /&gt;
             *out = ((offset &amp;gt;&amp;gt; 12) | 0xF000) &amp;lt;&amp;lt; 16 | ((offset &amp;lt;&amp;lt; 4) &amp;gt;&amp;gt; 5) | 0xE800;&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_CALL) {&lt;br /&gt;
         // +-32MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x2000000 || branchOffset &amp;lt;= -0x2000000)&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         if (base &amp;amp; 1) {&lt;br /&gt;
             *out = ((offset &amp;lt;&amp;lt; 23) &amp;amp; 0x1000000) | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8) | 0xFA000000;&lt;br /&gt;
         } else {&lt;br /&gt;
             *out = 0xEB000000 | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8);&lt;br /&gt;
         }&lt;br /&gt;
 &lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_JUMP24) {&lt;br /&gt;
         // +-32MB.&lt;br /&gt;
         if (branchOffset &amp;gt;= 0x2000000 || branchOffset &amp;lt;= -0x2000000 || (base &amp;amp; 1))&lt;br /&gt;
             return 0xD9012C23;&lt;br /&gt;
 &lt;br /&gt;
         *out = (*out &amp;amp; 0xFF000000) | ((offset &amp;lt;&amp;lt; 6) &amp;gt;&amp;gt; 8);&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (patchType == R_ARM_PREL31) {&lt;br /&gt;
         *out = addend + ((base &amp;lt;&amp;lt; 1) &amp;gt;&amp;gt; 1) - inputPtr;&lt;br /&gt;
         return 0;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     return 0xD9012C22;&lt;br /&gt;
 }&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23621</id>
		<title>GPU/External Registers</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23621"/>
		<updated>2025-07-22T18:53:23Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Update T2T constraints&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the address range accessible from the ARM11, used to configure the basic GPU functionality. For information about the internal registers used for 3D rendering, see [[GPU/Internal Registers]].&lt;br /&gt;
&lt;br /&gt;
== Map ==&lt;br /&gt;
Address mappings for the external registers. GSPGPU:WriteHWRegs takes these addresses relative to 0x1EB00000. &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! User VA&lt;br /&gt;
! PA&lt;br /&gt;
! Length&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00000&lt;br /&gt;
| 0x10400000&lt;br /&gt;
| 4&lt;br /&gt;
| Hardware ID&lt;br /&gt;
| Bit2: new model&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00004&lt;br /&gt;
| 0x10400004&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00010&lt;br /&gt;
| 0x10400010&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill1]] &amp;quot;PSC0&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00020&lt;br /&gt;
| 0x10400020&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill2]] &amp;quot;PSC1&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00030&lt;br /&gt;
| 0x10400030&lt;br /&gt;
| 4&lt;br /&gt;
| VRAM bank control&lt;br /&gt;
| Bits 8-11 = bank[i] disabled; other bits are unused.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00034&lt;br /&gt;
| 0x10400034&lt;br /&gt;
| 4&lt;br /&gt;
| GPU Busy&lt;br /&gt;
| Bit26 = PSC0, bit27 = PSC1, Bit30 = PPF, Bit31 = P3D&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00050&lt;br /&gt;
| 0x10400050&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x22221200 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00054&lt;br /&gt;
| 0x10400054&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFF2 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000C0&lt;br /&gt;
| 0x104000C0&lt;br /&gt;
| 4&lt;br /&gt;
| Backlight control&lt;br /&gt;
| Writes 0x0 to allow backlights to turn off, 0x20000000 to force them always on.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00400&lt;br /&gt;
| 0x10400400&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC0&amp;quot; (top screen)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00500&lt;br /&gt;
| 0x10400500&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC1&amp;quot; (bottom)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| 0x10400C00&lt;br /&gt;
| ?&lt;br /&gt;
| [[#Transfer_Engine|Transfer Engine]] &amp;quot;DMA&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|colspan=&amp;quot;5&amp;quot;| 0x1EF01000/0x10401000 - 0x1EF01C00/0x10401C00 maps to [[GPU/Internal_Registers|GPU internal registers]]. These registers are usually not read/written directly here, but are written using the command list interface below (corresponding to the GPUREG_CMDBUF_* internal registers)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01000&lt;br /&gt;
| 0x10401000&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0 on GPU init and before the Command List is used&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01080&lt;br /&gt;
| 0x10401080&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x12345678 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010C0&lt;br /&gt;
| 0x104010C0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFFFFFFF0 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010D0&lt;br /&gt;
| 0x104010D0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 1 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF014??&lt;br /&gt;
| 0x104014??&lt;br /&gt;
| 0x14&lt;br /&gt;
| &amp;quot;PPF&amp;quot; ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| 0x104018E0&lt;br /&gt;
| 0x14&lt;br /&gt;
| [[#Command_List|Command List]] &amp;quot;P3D&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Memory Fill ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  User VA&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X0&lt;br /&gt;
| Buffer start physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X4&lt;br /&gt;
| Buffer end physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X8&lt;br /&gt;
| Fill value&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000XC&lt;br /&gt;
| Control. bit0: start/busy, bit1: finished, bit8-9: fill-width (0=16bit, 1=3=24bit, 2=32bit)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Memory fills are used to initialize buffers in memory with a given value, similar to memset. A memory fill is triggered by setting bit0 in the control register. Doing so aborts any running memory fills on that filling unit. Upon completion, the hardware unsets bit0 and sets bit1 and fires interrupt PSC0.&lt;br /&gt;
&lt;br /&gt;
The addresses must be part of VRAM.&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP Shared Memory#GX SetMemoryFill|GX SetMemoryFill]].&lt;br /&gt;
&lt;br /&gt;
== LCD Source Framebuffer Setup ==&lt;br /&gt;
&lt;br /&gt;
All of these registers must be accessed with 32bit operations regardless of the registers&#039; actual bit size.&lt;br /&gt;
&lt;br /&gt;
The naming of these parameters reflects the physical characteristics of the displays, and not the way the 3DS is normally held.&lt;br /&gt;
&lt;br /&gt;
To make sense of these values, the 3DS must be held in a way, so that the bottom screen is in the left hand, and the top screen is in the right hand, and that way the first pixel will be in the top-left corner, as it should be. If the 3DS is held normally, the first pixel is in the bottom-left corner.&lt;br /&gt;
&lt;br /&gt;
All pixel and scanline timing values are 12bits, unless noted. This also applies to those fields where two u16 are combined into one register. Each u16 field is only 12bits in size. timin&lt;br /&gt;
&lt;br /&gt;
The horizontal timing parameter order is as follows (values may overflow through HTotal register value):&lt;br /&gt;
 0x10 &amp;lt; 0x14 &amp;lt;= 0x60.LO &amp;lt;= 0x04 &amp;lt;= 0x60.HI &amp;lt;= 0x08 &amp;lt;= 0x0C &amp;lt;= 0x10&lt;br /&gt;
 0x18 &amp;lt;= 0x60.LO&lt;br /&gt;
&lt;br /&gt;
Timing starts from HCount == 0, then each absolute value in the beforementioned register chain triggers when HCount == register, latching the primitive display controller into a new mode.&lt;br /&gt;
There is an inherent latch order, where if two simultenaous events occur, one event wins over another.&lt;br /&gt;
&lt;br /&gt;
 Known latched modes (in order):&lt;br /&gt;
 - HSync (triggers a line to the LCD to move to the next line)&lt;br /&gt;
 - Back porch (area between HSync and border being displayed, no pixels pushed, min 16 pixel clocks, otherwise the screen gets glitchy)&lt;br /&gt;
 - Left border start (no image data is being displayed, just a configurable solid color)&lt;br /&gt;
 - Image start (pixel data is being DMA&#039;d from video memory or main RAM)&lt;br /&gt;
 - Right border start/Image end (border color is being displayed after the main image)&lt;br /&gt;
 - Unknown synchronization (supposed to be probably right border end, but this mode seems to be broken or not do anything)&lt;br /&gt;
 - Front porch (no pixels pushed, 68 clock min, otherwise the screen doesn&#039;t sync properly, and really glitches out)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Offset&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| HTotal&lt;br /&gt;
| The total width of a timing scanline. In other words, this is the horizontal refresh clock divider value.&lt;br /&gt;
&lt;br /&gt;
HClock = PClock / (HTotal + 1)&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| HStart&lt;br /&gt;
| Determines when the image is going to be displayed in the visible region (register 0x60).&lt;br /&gt;
|-&lt;br /&gt;
| 0x08&lt;br /&gt;
| HBR&lt;br /&gt;
| Right border start(?). Does nothing.&lt;br /&gt;
&lt;br /&gt;
While this register seems to have no impact on the image whatsoever, it still has to be set to a valid value.&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| 0x0C&lt;br /&gt;
| HPF&lt;br /&gt;
| Front porch. The image is blanked during this period, and no pixels are pushed to the LCD.&lt;br /&gt;
&lt;br /&gt;
Unknown why, but a single dot of red is displayed before entering this mode.&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| HSync&lt;br /&gt;
| Triggers a HSync pulse.&lt;br /&gt;
&lt;br /&gt;
Based on behavior, this needs to last at least a pixel clock for the LCD to register the sync.&lt;br /&gt;
|-&lt;br /&gt;
| 0x14&lt;br /&gt;
| HPB&lt;br /&gt;
| Back porch? Has to be at least one bigger than HSync, otherwise HSync never triggers.&lt;br /&gt;
&lt;br /&gt;
The display is blank, and the LCD displays nothing in this period (doesn&#039;t push pixels).&lt;br /&gt;
|-&lt;br /&gt;
| 0x18&lt;br /&gt;
| HBL&lt;br /&gt;
| Left border trigger treshold. Enables pushing pixels to the display.&lt;br /&gt;
&lt;br /&gt;
If this value is smaller than the back porch, then the back porch period will be zero, and the border will be immediately displayed upon entering the back porch period.&lt;br /&gt;
&lt;br /&gt;
Can be lower than HSync, as the back porch is what takes the controller out of HSync.&lt;br /&gt;
&lt;br /&gt;
Must be &amp;lt;= HDisp start (reg 0x60 low u16), otherwise no pixels will be pushed due to a glitched state.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1C&lt;br /&gt;
| H Interrupt timing&lt;br /&gt;
| Made up from two u16 values, PDC interrupt line is asserted when HCount == low u16, and most likely deasserted when HCount == high u16.&lt;br /&gt;
&lt;br /&gt;
There seems to be some limitations though:&lt;br /&gt;
* low u16 must be smaller than high u16&lt;br /&gt;
* if low u16 is less than HTotal then high u16 must also be smaller than HTotal&lt;br /&gt;
* setting low u16 to &amp;gt;= HTotal disables the interrupt ever firing&lt;br /&gt;
&lt;br /&gt;
This is configured by gsp in a way so that low u16 equals to HTotal, meaning the HSync interrupt will never fire.&lt;br /&gt;
|-&lt;br /&gt;
| 0x20&lt;br /&gt;
| low u16: ???&lt;br /&gt;
high u16: ???&lt;br /&gt;
| ???&lt;br /&gt;
|-&lt;br /&gt;
| 0x24&lt;br /&gt;
| VTotal&lt;br /&gt;
| Total height of the timing window. Can be interpreted as the vertical clock divider.&lt;br /&gt;
&lt;br /&gt;
VClock = PClock / (HTotal + 1) / (VTotal + 1)&lt;br /&gt;
&lt;br /&gt;
Setting this to 494 lowers framerate to about 50.040660858 Hz ((268111856 / 24) / (450 + 1) / (494 + 1)).&lt;br /&gt;
|-&lt;br /&gt;
| 0x28&lt;br /&gt;
| ?&lt;br /&gt;
| Seems to determine the vertical blanking interval.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Setting this to lower than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will cut off the top &amp;lt;code&amp;gt;VTotal - VDisp - thisvalue&amp;lt;/code&amp;gt; lines.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will make the image be pushed downwards with the overscan color visible.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;HTotal&amp;lt;/code&amp;gt; will make the GPU skip vertical pixel data synchronization (hence filling the screen with the rest of the pixel data past the given screen framebuffer size). Also will skip &amp;lt;code&amp;gt;thisvalue + somevalue - HTotal&amp;lt;/code&amp;gt; lines into the &amp;quot;global&amp;quot; pixel buffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x30&lt;br /&gt;
| ?&lt;br /&gt;
| Total amount of vertical scanlines in the pixel buffer, must be bigger than *an unknown blanking-like value*. If this value is less than VDisp then the last two scanlines will be repeated interlaced until VDisp is reached.&lt;br /&gt;
|-&lt;br /&gt;
| 0x34&lt;br /&gt;
| VDisp(?)&lt;br /&gt;
| Total amonut of vertical scanlines displayed (only for top screen it seems like). If this value is less than VTotal then the rest of the scanlines will not be updated on the screen, so those will slowly fade out. Must be bigger than *an unknown blanking-like value*, otherwise an underflow will happen.&lt;br /&gt;
|-&lt;br /&gt;
| 0x38&lt;br /&gt;
| Vertical data offset(?)&lt;br /&gt;
| ??? Seems to offset the screen upwards if this value is high enough. If this value is higher or equal to *some value* (aka. if less than one scanline is displayed on the screen) then the screen will lose synchronization.&lt;br /&gt;
|-&lt;br /&gt;
| 0x40&lt;br /&gt;
| V Interrupt timing&lt;br /&gt;
| Similar to H Interrupt timing (0x1C), except the comparison is done against VCount, the limitations are emposed on VTotal, and the interrupt that fires is VSync.&lt;br /&gt;
&lt;br /&gt;
One important note is that it seems like the VSync interrupt always fires at HCount == 0, and there doesn&#039;t seem to be a register to control this behavior.&lt;br /&gt;
|-&lt;br /&gt;
| 0x44&lt;br /&gt;
| ???&lt;br /&gt;
| similar functionality to 0x10&lt;br /&gt;
|-&lt;br /&gt;
| 0x48&lt;br /&gt;
| ???&lt;br /&gt;
| bit0 seems to disable HSync, bit8 seems to disable VSync, rest of the bits aren&#039;t writable.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4C&lt;br /&gt;
| Overscan filler color&lt;br /&gt;
| 24bits(? top 8bits ignored)&lt;br /&gt;
&lt;br /&gt;
When the visible region is being drawn, but the timing parameters are set up in a way that the framebuffer is smaller than the visible region, it will be filled by this color.&lt;br /&gt;
|-&lt;br /&gt;
| 0x50&lt;br /&gt;
| HCount&lt;br /&gt;
| Horizontal &amp;quot;beam position&amp;quot; counter. Note that this value does not equal to the current pixel being drawn.&lt;br /&gt;
|-&lt;br /&gt;
| 0x54&lt;br /&gt;
| VCount&lt;br /&gt;
| Vertical &amp;quot;beam position&amp;quot; counter. Note that the scanline being drawn isn&#039;t equal to this value.&lt;br /&gt;
|-&lt;br /&gt;
| 0x5C&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: Image width (including some offset?)&lt;br /&gt;
high u16: Image height??? (seems to be unused)&lt;br /&gt;
|-&lt;br /&gt;
| 0x60&lt;br /&gt;
| HDisp&lt;br /&gt;
| low u16: Image start (border --&amp;gt; pixel data)&lt;br /&gt;
high u16: Image end (pixel data --&amp;gt; border)&lt;br /&gt;
|-&lt;br /&gt;
| 0x64&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: unknown&lt;br /&gt;
high u16: framebuffer total height (amount of scanlines blitted regardless of framebuffer height)&lt;br /&gt;
|-&lt;br /&gt;
| 0x68&lt;br /&gt;
| Framebuffer A first address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x6C&lt;br /&gt;
| Framebuffer A second address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x70&lt;br /&gt;
| Framebuffer format and other settings&lt;br /&gt;
| See [[#Framebuffer_format|framebuffer format]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x74&lt;br /&gt;
| PDC control&lt;br /&gt;
| Bit 0: Enable display controller.&lt;br /&gt;
Bit 8: HBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 9: VBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 10: Error IRQ mask? (0 = enabled).&lt;br /&gt;
Bit 16: Output enable?&lt;br /&gt;
|-&lt;br /&gt;
| 0x78&lt;br /&gt;
| Framebuffer select and status&lt;br /&gt;
| Bit 0: Next framebuffer to display (after VBlank).&lt;br /&gt;
Bit 4: Currently displaying framebuffer?&lt;br /&gt;
Bit 8: Reset FIFO?&lt;br /&gt;
Bit 16: HBlank IRQ status/ack. Write 1 to aknowledge.&lt;br /&gt;
Bit 17: VBlank IRQ status/ack.&lt;br /&gt;
Bit 18: Error IRQ status/ack?&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| Color lookup table index select&lt;br /&gt;
| 8bits, write-only&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| Color lookup table indexed element&lt;br /&gt;
| Contains the value of the color lookup table indexed by the above register, 24bits, RGB8 (0x00BBGGRR)  &lt;br /&gt;
Accessing this register will increase the index register by one&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| Framebuffer stride&lt;br /&gt;
| 32bits (bottom 3bits ignored?)&lt;br /&gt;
&lt;br /&gt;
Distance in bytes between the start of two framebuffer rows (must be a multiple of 8).&lt;br /&gt;
&lt;br /&gt;
In other words, this can be interpreted as the amount to add to the framebuffer pointer after displaying a scanline.&lt;br /&gt;
&lt;br /&gt;
Setting this to zero will cause only the first line of the image to be displayed repeated on the entire display. With the HSync interrupt it&#039;s possible to &amp;quot;race the beam&amp;quot; to (ab)use this feature.&lt;br /&gt;
&lt;br /&gt;
Because of this simplicity, writing a negative value here VFlips the image, although that requires the framebuffer pointer register to be set to the start of the last scanline, instead of at the start of the framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| Framebuffer B first address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| Framebuffer B second address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer format ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 2-0&lt;br /&gt;
| [[#Framebuffer_color_formats|Color format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5-4&lt;br /&gt;
| Framebuffer interlacing mode&lt;br /&gt;
&lt;br /&gt;
 0 - A  (no interlacing)&lt;br /&gt;
 1 - AA (scanline doubling)&lt;br /&gt;
 2 - AB (interlace enable)&lt;br /&gt;
 3 - BA (same as above, but the fields are inverted)&lt;br /&gt;
&lt;br /&gt;
In AB and BA interlace modes, a scanline from each framebuffer is output in an alternating manner. In AB mode, Framebuffer A is output on the frist display scanline. Similarly, in BA mode, Framebuffer B gets output to the first display scanline.&lt;br /&gt;
&lt;br /&gt;
The way AB and BA modes work, is that a scanline is output, the framebuffer stride value is added to the internal scanline pointer value, and the other framebuffer is selected. And this alternates until the end of the draw region.&lt;br /&gt;
&lt;br /&gt;
AA interlacing works like AB interlacing, except both internal framebuffer pointers are set to the Framebuffer A pointer value.&lt;br /&gt;
&lt;br /&gt;
In A mode (no interlacing), it doesn&#039;t switch to the other framebuffer at the end of outpuitting a scanline to the display.&lt;br /&gt;
&lt;br /&gt;
Bottom screen has this set to 0 (A mode, no interlacing) at all times.  &lt;br /&gt;
Top screen uses AB interlacing in 3D mode (with 3D slider enabled), and A mode (no interlacing) in 2D mode.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Alternative pixel output mode*&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 9-8&lt;br /&gt;
| DMA size&lt;br /&gt;
&lt;br /&gt;
 0 -  4 FCRAM words (32 bytes)&lt;br /&gt;
 1 -  8 FCRAM words (64 bytes)&lt;br /&gt;
 2 - 16 FCRAM words (128 bytes)&lt;br /&gt;
 3 - ???&lt;br /&gt;
&lt;br /&gt;
FCRAM doesn&#039;t support DMA size 3, as it can only burst up to 16 words (128 bytes), and will show a black screen instead.&lt;br /&gt;
|-&lt;br /&gt;
| 31-16&lt;br /&gt;
| Unknown&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt; The weird thing about bit6, is that it works different between the bottom and top LCD. On the bottom LCD, it doubles the number of outputted pixels (so the same pixel is outputted twice, effectively doing pixel/column doubling). However on the top screen, it does scanline doubling instead.  &lt;br /&gt;
Most likely the top screen receives two pixels at once per clock unit, outputting two scanlines simultaneously.&lt;br /&gt;
&lt;br /&gt;
On a 2DS, it seems to have no effect on the top part of the display, and on the bottom screen it just shifts the framebuffer to the right two pixels.&lt;br /&gt;
&lt;br /&gt;
GSP module only allows the LCD stereoscopy (3D) to be enabled when bit5=1 and bit6=0 here. When GSP module updates this register, GSP module will automatically disable the stereoscopy if those bits are not set for enabling stereoscopy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When both interlacing and alternative mode is disabled (bit6=0), the full resolution of the top screen (240x800) can be utilized if the PDC registers are updated to accomodate this higher resolution. GSP contains tables for this mode (gsp mode == 1). GSP automatically applies this mode if both bit5 and bit6 are cleared. This is also the default, and the only valid mode for the bottom screen in userland.&lt;br /&gt;
&lt;br /&gt;
If only AB interlacing is enabled (bit5=1, bit6=0), gsp detects this as a request to switch to 3D mode (gsp mode == 2), and enables the parallax barrier.  &lt;br /&gt;
It&#039;s unknown how to control this, but some other PDC registers control if interlacing should be done by true interleaving (both framebuffers are treated as 240x400), or by skipping lines (both framebuffers are treated as 240x800).&lt;br /&gt;
&lt;br /&gt;
If only alternative mode is enabled (bit5=0, bit6=1), gsp detects it as a request to switch back to 2D mode for the top screen (gsp mode == 0). This is also the default mode for the top screen.&lt;br /&gt;
&lt;br /&gt;
Both interlacing and scan doubling can&#039;t be enabled in usermode, but it works as expected in baremetal.&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer color formats ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Value&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| GL_RGBA8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| GL_RGB8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| GL_RGB565_OES&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| GL_RGB5_A1_OES&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| GL_RGBA4_OES&lt;br /&gt;
|}&lt;br /&gt;
Color components are laid out in reverse byte order, with the most significant bits used first (i.e. non-24-bit pixels are stored as a little-endian values). For instance, a raw data stream of two GL_RGB565_OES pixels looks like GGGBBBBB RRRRRGGG GGGBBBBB RRRRRGGG.&lt;br /&gt;
&lt;br /&gt;
Color formats 5, 6, and 7 are blocked by gsp, but they behave as pixel-doubled RGBA8 (not line doubling, but instead the same pixel is output twice) if used outside of userland.&lt;br /&gt;
&lt;br /&gt;
== Transfer Engine ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| Input physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C04&lt;br /&gt;
| Output physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C08&lt;br /&gt;
| DisplayTransfer output width (bits 0-15) and height (bits 16-31)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C0C&lt;br /&gt;
| DisplayTransfer input width and height&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C10&lt;br /&gt;
| Transfer flags&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C14&lt;br /&gt;
| ?, GSP writes value 0 here prior to writing to 0x1EF00C18 for DisplayTransfer&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C18&lt;br /&gt;
|  Setting bit0 starts the transfer; upon completion, bit0 is unset and bit8 is set&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C1C&lt;br /&gt;
|  ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C20&lt;br /&gt;
| TextureCopy total amount of data to copy, in bytes&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C24&lt;br /&gt;
| TextureCopy input line width (bits 0-15) and gap (bits 16-31), in 16 byte units&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C28&lt;br /&gt;
| TextureCopy output line width and gap&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Transfer flags:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| When set, the framebuffer data is flipped vertically&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Linear-&amp;gt;tiled mode (overrides tiled-&amp;gt;linear mode)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| This bit is required when the output width is less than the input width for the hardware to properly crop the lines, otherwise the output will be mis-aligned&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| TextureCopy mode (overrides all other modes)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Tiled-&amp;gt;tiled mode (overrides tiled-&amp;gt;linear, linear-&amp;gt;tiled modes)&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 10-8&lt;br /&gt;
| Input [[GPU/External_Registers#Framebuffer_color_formats|color format]]&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 14-12&lt;br /&gt;
| Output color format&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| Use 32x32 block tiling mode, instead of the usual 8x8 one (output dimensions must be multiples of 32, even if cropping with bit 2 set above)&lt;br /&gt;
|-&lt;br /&gt;
| 17-23&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 24-25&lt;br /&gt;
| Scale down the input image using a box filter (0 = No downscale, 1 = 2x1 downscale, 2 = 2x2 downscale, 3 = invalid)&lt;br /&gt;
|-&lt;br /&gt;
| 31-26&lt;br /&gt;
| Not writable&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP_Shared_Memory#Commands|GSP]] for DisplayTransfer and TextureCopy. TextureCopy registers are only used in TextureCopy mode; likewise, DisplayTransfer registers are only used when TextureCopy mode is not set. By default, DisplayTransfer will work in tiled-&amp;gt;linear mode.&lt;br /&gt;
&lt;br /&gt;
=== Tiled to linear ===&lt;br /&gt;
&lt;br /&gt;
Unswizzles the input buffer, this is usually used for transferring GPU framebuffer data onto LCD framebuffers. The following constraints apply:&lt;br /&gt;
&lt;br /&gt;
* Output dimensions must not be bigger than input ones.&lt;br /&gt;
* Width dimensions must be &amp;gt;= 64.&lt;br /&gt;
* Height dimensions must be &amp;gt;= 16.&lt;br /&gt;
* Width dimensions are required to be aligned to 16 bytes when doing RGB8 transfers.&lt;br /&gt;
** Otherwise they are required to be aligned to 8 bytes.&lt;br /&gt;
* If downscale is used, input and output dimensions should be the same (otherwise the output is glitched), and width/2 must also follow alignment constraints.&lt;br /&gt;
&lt;br /&gt;
Format conversion results:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Conversion&lt;br /&gt;
!  Result&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Tiled to tiled ===&lt;br /&gt;
&lt;br /&gt;
Officially this is always used with 2x2 downscale, other configurations give glitched output. Hence, this is used for antialiasing and mipmap generation.&lt;br /&gt;
&lt;br /&gt;
The following constraints apply:&lt;br /&gt;
&lt;br /&gt;
* Output dimensions should not be bigger than input ones, otherwise the output is glitched.&lt;br /&gt;
* Width dimensions must be &amp;gt;= 64.&lt;br /&gt;
* Height dimensions must be &amp;gt;= 32.&lt;br /&gt;
* Width dimensions are required to be aligned to 64 bytes when doing RGB8/RGBA8 transfers.&lt;br /&gt;
** Otherwise they are required to be aligned to 128 bytes.&lt;br /&gt;
&lt;br /&gt;
Format conversion results: same as tiled-&amp;gt;linear.&lt;br /&gt;
&lt;br /&gt;
=== TextureCopy ===&lt;br /&gt;
&lt;br /&gt;
When bit 3 of the control register is set, the hardware performs a TextureCopy-mode transfer: no format conversions are done, instead a raw data copy is performed from the source to the destination, with a configurable gap between lines. All bits of the control register are ignored, except for input/output dimensions, which are used for line width and gap, and bit 2, which must be set when gaps are used.&lt;br /&gt;
&lt;br /&gt;
The total amount of bytes to copy is specified in the size register, the hardware loops reading lines from the input and writing them to the output until this amount is copied. The gap specifies the number of bytes to skip after each line read (a gap of 0 results in a contiguous read). Gaps do not count towards the total size of the transfer.&lt;br /&gt;
&lt;br /&gt;
When setting line width and gap they must be divided by 2 (it can be thought as the calculation being done in bits, and the values being stripped of their lower 4 bits for the alignment). For example, if the left half of a 32x32 RGB8 texture is to be copied, the parameters will be:&lt;br /&gt;
 line width = (16 * 24) &amp;gt;&amp;gt; 4 = 24&lt;br /&gt;
 gap = line width&lt;br /&gt;
 size = 16 * 32 * 3 = 1536&lt;br /&gt;
&lt;br /&gt;
By correctly calculating the input and output gap sizes it is possible to use this functionality to copy arbitrary sub-rectangles between differently-sized framebuffers or textures, which is one of its main uses over a regular no-conversion DisplayTransfer. When copying tiled textures/framebuffers it&#039;s important to remember that the contents of a tile are laid out sequentially in memory, and so this should be taken into account when calculating the transfer parameters.&lt;br /&gt;
&lt;br /&gt;
Specifying invalid/junk values for the TextureCopy dimensions can result in the GPU hanging while attempting to process this TextureCopy. For instance, when in contiguous mode the size must be at least 16; when in gap mode, the size must be at least 192, and the line width must not be 0.&lt;br /&gt;
&lt;br /&gt;
== Command List ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| Buffer size in bytes &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E8&lt;br /&gt;
| Buffer physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018F0&lt;br /&gt;
| Setting bit0 to 1 enables processing GPU command execution. Upon completion, bit0 seems to be reset to 0.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These 3 registers are used by [[GSP_Shared_Memory|GX command]] 1. This is used for [[GPU/Internal_Registers|GPU commands]].&lt;br /&gt;
&lt;br /&gt;
== Framebuffers ==&lt;br /&gt;
These LCD framebuffers normally contain the last rendered frames from the GPU. The framebuffers are drawn from left-to-right, instead of top-to-bottom.(Thus the beginning of the framebuffer is drawn starting at the left side of the screen)&lt;br /&gt;
&lt;br /&gt;
Both of the 3D screen left/right framebuffers are displayed regardless of the 3D slider&#039;s state, however when the 3D slider is set to &amp;quot;off&amp;quot; the 3D effect is disabled. Normally when the 3D slider&#039;s state is set to &amp;quot;off&amp;quot; the left/right framebuffer addresses are set to the same physical address. When the 3D effect is disabled and the left/right framebuffers are set to separate addresses, the LCD seems to alternate between displaying the left/right framebuffer each frame.&lt;br /&gt;
&lt;br /&gt;
==== Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00400 = 0x1C2&lt;br /&gt;
* 0x1EF00404 = 0xD1&lt;br /&gt;
* 0x1EF00408 = 0x1C1&lt;br /&gt;
* 0x1EF0040C = 0x1C1&lt;br /&gt;
* 0x1EF00410 = 0&lt;br /&gt;
* 0x1EF00414 = 0xCF&lt;br /&gt;
* 0x1EF00418 = 0xD1&lt;br /&gt;
* 0x1EF0041C = 0x1C501C1&lt;br /&gt;
* 0x1EF00420 = 0x10000&lt;br /&gt;
* 0x1EF00424 = 0x19D&lt;br /&gt;
* 0x1EF00428 = 2&lt;br /&gt;
* 0x1EF0042C = 0x1C2&lt;br /&gt;
* 0x1EF00430 = 0x1C2&lt;br /&gt;
* 0x1EF00434 = 0x1C2&lt;br /&gt;
* 0x1EF00438 = 1&lt;br /&gt;
* 0x1EF0043C = 2&lt;br /&gt;
* 0x1EF00440 = 0x1960192&lt;br /&gt;
* 0x1EF00444 = 0&lt;br /&gt;
* 0x1EF00448 = 0&lt;br /&gt;
* 0x1EF0045C = 0x19000F0&lt;br /&gt;
* 0x1EF00460 = 0x1c100d1&lt;br /&gt;
* 0x1EF00464 = 0x1920002&lt;br /&gt;
* 0x1EF00470 = 0x80340&lt;br /&gt;
* 0x1EF0049C = 0&lt;br /&gt;
&lt;br /&gt;
==== More Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00468 = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF0046C = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF00494 = 0x18300000&lt;br /&gt;
* 0x1EF00498 = 0x18300000&lt;br /&gt;
* 0x1EF00478 = 1, doesn&#039;t stay 1, read as 0&lt;br /&gt;
* 0x1EF00474 = 0x10501&lt;br /&gt;
&lt;br /&gt;
[[Category:GPU]]&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23620</id>
		<title>GPU/External Registers</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23620"/>
		<updated>2025-07-22T10:40:53Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: T2T&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the address range accessible from the ARM11, used to configure the basic GPU functionality. For information about the internal registers used for 3D rendering, see [[GPU/Internal Registers]].&lt;br /&gt;
&lt;br /&gt;
== Map ==&lt;br /&gt;
Address mappings for the external registers. GSPGPU:WriteHWRegs takes these addresses relative to 0x1EB00000. &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! User VA&lt;br /&gt;
! PA&lt;br /&gt;
! Length&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00000&lt;br /&gt;
| 0x10400000&lt;br /&gt;
| 4&lt;br /&gt;
| Hardware ID&lt;br /&gt;
| Bit2: new model&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00004&lt;br /&gt;
| 0x10400004&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00010&lt;br /&gt;
| 0x10400010&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill1]] &amp;quot;PSC0&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00020&lt;br /&gt;
| 0x10400020&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill2]] &amp;quot;PSC1&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00030&lt;br /&gt;
| 0x10400030&lt;br /&gt;
| 4&lt;br /&gt;
| VRAM bank control&lt;br /&gt;
| Bits 8-11 = bank[i] disabled; other bits are unused.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00034&lt;br /&gt;
| 0x10400034&lt;br /&gt;
| 4&lt;br /&gt;
| GPU Busy&lt;br /&gt;
| Bit26 = PSC0, bit27 = PSC1, Bit30 = PPF, Bit31 = P3D&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00050&lt;br /&gt;
| 0x10400050&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x22221200 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00054&lt;br /&gt;
| 0x10400054&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFF2 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000C0&lt;br /&gt;
| 0x104000C0&lt;br /&gt;
| 4&lt;br /&gt;
| Backlight control&lt;br /&gt;
| Writes 0x0 to allow backlights to turn off, 0x20000000 to force them always on.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00400&lt;br /&gt;
| 0x10400400&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC0&amp;quot; (top screen)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00500&lt;br /&gt;
| 0x10400500&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC1&amp;quot; (bottom)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| 0x10400C00&lt;br /&gt;
| ?&lt;br /&gt;
| [[#Transfer_Engine|Transfer Engine]] &amp;quot;DMA&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|colspan=&amp;quot;5&amp;quot;| 0x1EF01000/0x10401000 - 0x1EF01C00/0x10401C00 maps to [[GPU/Internal_Registers|GPU internal registers]]. These registers are usually not read/written directly here, but are written using the command list interface below (corresponding to the GPUREG_CMDBUF_* internal registers)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01000&lt;br /&gt;
| 0x10401000&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0 on GPU init and before the Command List is used&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01080&lt;br /&gt;
| 0x10401080&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x12345678 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010C0&lt;br /&gt;
| 0x104010C0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFFFFFFF0 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010D0&lt;br /&gt;
| 0x104010D0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 1 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF014??&lt;br /&gt;
| 0x104014??&lt;br /&gt;
| 0x14&lt;br /&gt;
| &amp;quot;PPF&amp;quot; ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| 0x104018E0&lt;br /&gt;
| 0x14&lt;br /&gt;
| [[#Command_List|Command List]] &amp;quot;P3D&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Memory Fill ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  User VA&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X0&lt;br /&gt;
| Buffer start physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X4&lt;br /&gt;
| Buffer end physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X8&lt;br /&gt;
| Fill value&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000XC&lt;br /&gt;
| Control. bit0: start/busy, bit1: finished, bit8-9: fill-width (0=16bit, 1=3=24bit, 2=32bit)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Memory fills are used to initialize buffers in memory with a given value, similar to memset. A memory fill is triggered by setting bit0 in the control register. Doing so aborts any running memory fills on that filling unit. Upon completion, the hardware unsets bit0 and sets bit1 and fires interrupt PSC0.&lt;br /&gt;
&lt;br /&gt;
The addresses must be part of VRAM.&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP Shared Memory#GX SetMemoryFill|GX SetMemoryFill]].&lt;br /&gt;
&lt;br /&gt;
== LCD Source Framebuffer Setup ==&lt;br /&gt;
&lt;br /&gt;
All of these registers must be accessed with 32bit operations regardless of the registers&#039; actual bit size.&lt;br /&gt;
&lt;br /&gt;
The naming of these parameters reflects the physical characteristics of the displays, and not the way the 3DS is normally held.&lt;br /&gt;
&lt;br /&gt;
To make sense of these values, the 3DS must be held in a way, so that the bottom screen is in the left hand, and the top screen is in the right hand, and that way the first pixel will be in the top-left corner, as it should be. If the 3DS is held normally, the first pixel is in the bottom-left corner.&lt;br /&gt;
&lt;br /&gt;
All pixel and scanline timing values are 12bits, unless noted. This also applies to those fields where two u16 are combined into one register. Each u16 field is only 12bits in size. timin&lt;br /&gt;
&lt;br /&gt;
The horizontal timing parameter order is as follows (values may overflow through HTotal register value):&lt;br /&gt;
 0x10 &amp;lt; 0x14 &amp;lt;= 0x60.LO &amp;lt;= 0x04 &amp;lt;= 0x60.HI &amp;lt;= 0x08 &amp;lt;= 0x0C &amp;lt;= 0x10&lt;br /&gt;
 0x18 &amp;lt;= 0x60.LO&lt;br /&gt;
&lt;br /&gt;
Timing starts from HCount == 0, then each absolute value in the beforementioned register chain triggers when HCount == register, latching the primitive display controller into a new mode.&lt;br /&gt;
There is an inherent latch order, where if two simultenaous events occur, one event wins over another.&lt;br /&gt;
&lt;br /&gt;
 Known latched modes (in order):&lt;br /&gt;
 - HSync (triggers a line to the LCD to move to the next line)&lt;br /&gt;
 - Back porch (area between HSync and border being displayed, no pixels pushed, min 16 pixel clocks, otherwise the screen gets glitchy)&lt;br /&gt;
 - Left border start (no image data is being displayed, just a configurable solid color)&lt;br /&gt;
 - Image start (pixel data is being DMA&#039;d from video memory or main RAM)&lt;br /&gt;
 - Right border start/Image end (border color is being displayed after the main image)&lt;br /&gt;
 - Unknown synchronization (supposed to be probably right border end, but this mode seems to be broken or not do anything)&lt;br /&gt;
 - Front porch (no pixels pushed, 68 clock min, otherwise the screen doesn&#039;t sync properly, and really glitches out)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Offset&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| HTotal&lt;br /&gt;
| The total width of a timing scanline. In other words, this is the horizontal refresh clock divider value.&lt;br /&gt;
&lt;br /&gt;
HClock = PClock / (HTotal + 1)&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| HStart&lt;br /&gt;
| Determines when the image is going to be displayed in the visible region (register 0x60).&lt;br /&gt;
|-&lt;br /&gt;
| 0x08&lt;br /&gt;
| HBR&lt;br /&gt;
| Right border start(?). Does nothing.&lt;br /&gt;
&lt;br /&gt;
While this register seems to have no impact on the image whatsoever, it still has to be set to a valid value.&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| 0x0C&lt;br /&gt;
| HPF&lt;br /&gt;
| Front porch. The image is blanked during this period, and no pixels are pushed to the LCD.&lt;br /&gt;
&lt;br /&gt;
Unknown why, but a single dot of red is displayed before entering this mode.&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| HSync&lt;br /&gt;
| Triggers a HSync pulse.&lt;br /&gt;
&lt;br /&gt;
Based on behavior, this needs to last at least a pixel clock for the LCD to register the sync.&lt;br /&gt;
|-&lt;br /&gt;
| 0x14&lt;br /&gt;
| HPB&lt;br /&gt;
| Back porch? Has to be at least one bigger than HSync, otherwise HSync never triggers.&lt;br /&gt;
&lt;br /&gt;
The display is blank, and the LCD displays nothing in this period (doesn&#039;t push pixels).&lt;br /&gt;
|-&lt;br /&gt;
| 0x18&lt;br /&gt;
| HBL&lt;br /&gt;
| Left border trigger treshold. Enables pushing pixels to the display.&lt;br /&gt;
&lt;br /&gt;
If this value is smaller than the back porch, then the back porch period will be zero, and the border will be immediately displayed upon entering the back porch period.&lt;br /&gt;
&lt;br /&gt;
Can be lower than HSync, as the back porch is what takes the controller out of HSync.&lt;br /&gt;
&lt;br /&gt;
Must be &amp;lt;= HDisp start (reg 0x60 low u16), otherwise no pixels will be pushed due to a glitched state.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1C&lt;br /&gt;
| H Interrupt timing&lt;br /&gt;
| Made up from two u16 values, PDC interrupt line is asserted when HCount == low u16, and most likely deasserted when HCount == high u16.&lt;br /&gt;
&lt;br /&gt;
There seems to be some limitations though:&lt;br /&gt;
* low u16 must be smaller than high u16&lt;br /&gt;
* if low u16 is less than HTotal then high u16 must also be smaller than HTotal&lt;br /&gt;
* setting low u16 to &amp;gt;= HTotal disables the interrupt ever firing&lt;br /&gt;
&lt;br /&gt;
This is configured by gsp in a way so that low u16 equals to HTotal, meaning the HSync interrupt will never fire.&lt;br /&gt;
|-&lt;br /&gt;
| 0x20&lt;br /&gt;
| low u16: ???&lt;br /&gt;
high u16: ???&lt;br /&gt;
| ???&lt;br /&gt;
|-&lt;br /&gt;
| 0x24&lt;br /&gt;
| VTotal&lt;br /&gt;
| Total height of the timing window. Can be interpreted as the vertical clock divider.&lt;br /&gt;
&lt;br /&gt;
VClock = PClock / (HTotal + 1) / (VTotal + 1)&lt;br /&gt;
&lt;br /&gt;
Setting this to 494 lowers framerate to about 50.040660858 Hz ((268111856 / 24) / (450 + 1) / (494 + 1)).&lt;br /&gt;
|-&lt;br /&gt;
| 0x28&lt;br /&gt;
| ?&lt;br /&gt;
| Seems to determine the vertical blanking interval.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Setting this to lower than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will cut off the top &amp;lt;code&amp;gt;VTotal - VDisp - thisvalue&amp;lt;/code&amp;gt; lines.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will make the image be pushed downwards with the overscan color visible.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;HTotal&amp;lt;/code&amp;gt; will make the GPU skip vertical pixel data synchronization (hence filling the screen with the rest of the pixel data past the given screen framebuffer size). Also will skip &amp;lt;code&amp;gt;thisvalue + somevalue - HTotal&amp;lt;/code&amp;gt; lines into the &amp;quot;global&amp;quot; pixel buffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x30&lt;br /&gt;
| ?&lt;br /&gt;
| Total amount of vertical scanlines in the pixel buffer, must be bigger than *an unknown blanking-like value*. If this value is less than VDisp then the last two scanlines will be repeated interlaced until VDisp is reached.&lt;br /&gt;
|-&lt;br /&gt;
| 0x34&lt;br /&gt;
| VDisp(?)&lt;br /&gt;
| Total amonut of vertical scanlines displayed (only for top screen it seems like). If this value is less than VTotal then the rest of the scanlines will not be updated on the screen, so those will slowly fade out. Must be bigger than *an unknown blanking-like value*, otherwise an underflow will happen.&lt;br /&gt;
|-&lt;br /&gt;
| 0x38&lt;br /&gt;
| Vertical data offset(?)&lt;br /&gt;
| ??? Seems to offset the screen upwards if this value is high enough. If this value is higher or equal to *some value* (aka. if less than one scanline is displayed on the screen) then the screen will lose synchronization.&lt;br /&gt;
|-&lt;br /&gt;
| 0x40&lt;br /&gt;
| V Interrupt timing&lt;br /&gt;
| Similar to H Interrupt timing (0x1C), except the comparison is done against VCount, the limitations are emposed on VTotal, and the interrupt that fires is VSync.&lt;br /&gt;
&lt;br /&gt;
One important note is that it seems like the VSync interrupt always fires at HCount == 0, and there doesn&#039;t seem to be a register to control this behavior.&lt;br /&gt;
|-&lt;br /&gt;
| 0x44&lt;br /&gt;
| ???&lt;br /&gt;
| similar functionality to 0x10&lt;br /&gt;
|-&lt;br /&gt;
| 0x48&lt;br /&gt;
| ???&lt;br /&gt;
| bit0 seems to disable HSync, bit8 seems to disable VSync, rest of the bits aren&#039;t writable.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4C&lt;br /&gt;
| Overscan filler color&lt;br /&gt;
| 24bits(? top 8bits ignored)&lt;br /&gt;
&lt;br /&gt;
When the visible region is being drawn, but the timing parameters are set up in a way that the framebuffer is smaller than the visible region, it will be filled by this color.&lt;br /&gt;
|-&lt;br /&gt;
| 0x50&lt;br /&gt;
| HCount&lt;br /&gt;
| Horizontal &amp;quot;beam position&amp;quot; counter. Note that this value does not equal to the current pixel being drawn.&lt;br /&gt;
|-&lt;br /&gt;
| 0x54&lt;br /&gt;
| VCount&lt;br /&gt;
| Vertical &amp;quot;beam position&amp;quot; counter. Note that the scanline being drawn isn&#039;t equal to this value.&lt;br /&gt;
|-&lt;br /&gt;
| 0x5C&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: Image width (including some offset?)&lt;br /&gt;
high u16: Image height??? (seems to be unused)&lt;br /&gt;
|-&lt;br /&gt;
| 0x60&lt;br /&gt;
| HDisp&lt;br /&gt;
| low u16: Image start (border --&amp;gt; pixel data)&lt;br /&gt;
high u16: Image end (pixel data --&amp;gt; border)&lt;br /&gt;
|-&lt;br /&gt;
| 0x64&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: unknown&lt;br /&gt;
high u16: framebuffer total height (amount of scanlines blitted regardless of framebuffer height)&lt;br /&gt;
|-&lt;br /&gt;
| 0x68&lt;br /&gt;
| Framebuffer A first address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x6C&lt;br /&gt;
| Framebuffer A second address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x70&lt;br /&gt;
| Framebuffer format and other settings&lt;br /&gt;
| See [[#Framebuffer_format|framebuffer format]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x74&lt;br /&gt;
| PDC control&lt;br /&gt;
| Bit 0: Enable display controller.&lt;br /&gt;
Bit 8: HBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 9: VBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 10: Error IRQ mask? (0 = enabled).&lt;br /&gt;
Bit 16: Output enable?&lt;br /&gt;
|-&lt;br /&gt;
| 0x78&lt;br /&gt;
| Framebuffer select and status&lt;br /&gt;
| Bit 0: Next framebuffer to display (after VBlank).&lt;br /&gt;
Bit 4: Currently displaying framebuffer?&lt;br /&gt;
Bit 8: Reset FIFO?&lt;br /&gt;
Bit 16: HBlank IRQ status/ack. Write 1 to aknowledge.&lt;br /&gt;
Bit 17: VBlank IRQ status/ack.&lt;br /&gt;
Bit 18: Error IRQ status/ack?&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| Color lookup table index select&lt;br /&gt;
| 8bits, write-only&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| Color lookup table indexed element&lt;br /&gt;
| Contains the value of the color lookup table indexed by the above register, 24bits, RGB8 (0x00BBGGRR)  &lt;br /&gt;
Accessing this register will increase the index register by one&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| Framebuffer stride&lt;br /&gt;
| 32bits (bottom 3bits ignored?)&lt;br /&gt;
&lt;br /&gt;
Distance in bytes between the start of two framebuffer rows (must be a multiple of 8).&lt;br /&gt;
&lt;br /&gt;
In other words, this can be interpreted as the amount to add to the framebuffer pointer after displaying a scanline.&lt;br /&gt;
&lt;br /&gt;
Setting this to zero will cause only the first line of the image to be displayed repeated on the entire display. With the HSync interrupt it&#039;s possible to &amp;quot;race the beam&amp;quot; to (ab)use this feature.&lt;br /&gt;
&lt;br /&gt;
Because of this simplicity, writing a negative value here VFlips the image, although that requires the framebuffer pointer register to be set to the start of the last scanline, instead of at the start of the framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| Framebuffer B first address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| Framebuffer B second address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer format ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 2-0&lt;br /&gt;
| [[#Framebuffer_color_formats|Color format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5-4&lt;br /&gt;
| Framebuffer interlacing mode&lt;br /&gt;
&lt;br /&gt;
 0 - A  (no interlacing)&lt;br /&gt;
 1 - AA (scanline doubling)&lt;br /&gt;
 2 - AB (interlace enable)&lt;br /&gt;
 3 - BA (same as above, but the fields are inverted)&lt;br /&gt;
&lt;br /&gt;
In AB and BA interlace modes, a scanline from each framebuffer is output in an alternating manner. In AB mode, Framebuffer A is output on the frist display scanline. Similarly, in BA mode, Framebuffer B gets output to the first display scanline.&lt;br /&gt;
&lt;br /&gt;
The way AB and BA modes work, is that a scanline is output, the framebuffer stride value is added to the internal scanline pointer value, and the other framebuffer is selected. And this alternates until the end of the draw region.&lt;br /&gt;
&lt;br /&gt;
AA interlacing works like AB interlacing, except both internal framebuffer pointers are set to the Framebuffer A pointer value.&lt;br /&gt;
&lt;br /&gt;
In A mode (no interlacing), it doesn&#039;t switch to the other framebuffer at the end of outpuitting a scanline to the display.&lt;br /&gt;
&lt;br /&gt;
Bottom screen has this set to 0 (A mode, no interlacing) at all times.  &lt;br /&gt;
Top screen uses AB interlacing in 3D mode (with 3D slider enabled), and A mode (no interlacing) in 2D mode.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Alternative pixel output mode*&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 9-8&lt;br /&gt;
| DMA size&lt;br /&gt;
&lt;br /&gt;
 0 -  4 FCRAM words (32 bytes)&lt;br /&gt;
 1 -  8 FCRAM words (64 bytes)&lt;br /&gt;
 2 - 16 FCRAM words (128 bytes)&lt;br /&gt;
 3 - ???&lt;br /&gt;
&lt;br /&gt;
FCRAM doesn&#039;t support DMA size 3, as it can only burst up to 16 words (128 bytes), and will show a black screen instead.&lt;br /&gt;
|-&lt;br /&gt;
| 31-16&lt;br /&gt;
| Unknown&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt; The weird thing about bit6, is that it works different between the bottom and top LCD. On the bottom LCD, it doubles the number of outputted pixels (so the same pixel is outputted twice, effectively doing pixel/column doubling). However on the top screen, it does scanline doubling instead.  &lt;br /&gt;
Most likely the top screen receives two pixels at once per clock unit, outputting two scanlines simultaneously.&lt;br /&gt;
&lt;br /&gt;
On a 2DS, it seems to have no effect on the top part of the display, and on the bottom screen it just shifts the framebuffer to the right two pixels.&lt;br /&gt;
&lt;br /&gt;
GSP module only allows the LCD stereoscopy (3D) to be enabled when bit5=1 and bit6=0 here. When GSP module updates this register, GSP module will automatically disable the stereoscopy if those bits are not set for enabling stereoscopy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When both interlacing and alternative mode is disabled (bit6=0), the full resolution of the top screen (240x800) can be utilized if the PDC registers are updated to accomodate this higher resolution. GSP contains tables for this mode (gsp mode == 1). GSP automatically applies this mode if both bit5 and bit6 are cleared. This is also the default, and the only valid mode for the bottom screen in userland.&lt;br /&gt;
&lt;br /&gt;
If only AB interlacing is enabled (bit5=1, bit6=0), gsp detects this as a request to switch to 3D mode (gsp mode == 2), and enables the parallax barrier.  &lt;br /&gt;
It&#039;s unknown how to control this, but some other PDC registers control if interlacing should be done by true interleaving (both framebuffers are treated as 240x400), or by skipping lines (both framebuffers are treated as 240x800).&lt;br /&gt;
&lt;br /&gt;
If only alternative mode is enabled (bit5=0, bit6=1), gsp detects it as a request to switch back to 2D mode for the top screen (gsp mode == 0). This is also the default mode for the top screen.&lt;br /&gt;
&lt;br /&gt;
Both interlacing and scan doubling can&#039;t be enabled in usermode, but it works as expected in baremetal.&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer color formats ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Value&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| GL_RGBA8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| GL_RGB8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| GL_RGB565_OES&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| GL_RGB5_A1_OES&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| GL_RGBA4_OES&lt;br /&gt;
|}&lt;br /&gt;
Color components are laid out in reverse byte order, with the most significant bits used first (i.e. non-24-bit pixels are stored as a little-endian values). For instance, a raw data stream of two GL_RGB565_OES pixels looks like GGGBBBBB RRRRRGGG GGGBBBBB RRRRRGGG.&lt;br /&gt;
&lt;br /&gt;
Color formats 5, 6, and 7 are blocked by gsp, but they behave as pixel-doubled RGBA8 (not line doubling, but instead the same pixel is output twice) if used outside of userland.&lt;br /&gt;
&lt;br /&gt;
== Transfer Engine ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| Input physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C04&lt;br /&gt;
| Output physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C08&lt;br /&gt;
| DisplayTransfer output width (bits 0-15) and height (bits 16-31)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C0C&lt;br /&gt;
| DisplayTransfer input width and height&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C10&lt;br /&gt;
| Transfer flags&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C14&lt;br /&gt;
| ?, GSP writes value 0 here prior to writing to 0x1EF00C18 for DisplayTransfer&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C18&lt;br /&gt;
|  Setting bit0 starts the transfer; upon completion, bit0 is unset and bit8 is set&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C1C&lt;br /&gt;
|  ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C20&lt;br /&gt;
| TextureCopy total amount of data to copy, in bytes&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C24&lt;br /&gt;
| TextureCopy input line width (bits 0-15) and gap (bits 16-31), in 16 byte units&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C28&lt;br /&gt;
| TextureCopy output line width and gap&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Transfer flags:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| When set, the framebuffer data is flipped vertically&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Linear-&amp;gt;tiled mode (overrides tiled-&amp;gt;linear mode)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| This bit is required when the output width is less than the input width for the hardware to properly crop the lines, otherwise the output will be mis-aligned&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| TextureCopy mode (overrides all other modes)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Tiled-&amp;gt;tiled mode (overrides tiled-&amp;gt;linear, linear-&amp;gt;tiled modes)&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 10-8&lt;br /&gt;
| Input [[GPU/External_Registers#Framebuffer_color_formats|color format]]&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 14-12&lt;br /&gt;
| Output color format&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| Use 32x32 block tiling mode, instead of the usual 8x8 one (output dimensions must be multiples of 32, even if cropping with bit 2 set above)&lt;br /&gt;
|-&lt;br /&gt;
| 17-23&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 24-25&lt;br /&gt;
| Scale down the input image using a box filter (0 = No downscale, 1 = 2x1 downscale, 2 = 2x2 downscale, 3 = invalid)&lt;br /&gt;
|-&lt;br /&gt;
| 31-26&lt;br /&gt;
| Not writable&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP_Shared_Memory#Commands|GSP]] for DisplayTransfer and TextureCopy. TextureCopy registers are only used in TextureCopy mode; likewise, DisplayTransfer registers are only used when TextureCopy mode is not set. By default, DisplayTransfer will work in tiled-&amp;gt;linear mode.&lt;br /&gt;
&lt;br /&gt;
=== Tiled to linear ===&lt;br /&gt;
&lt;br /&gt;
Unswizzles the input buffer, this is usually used for transferring GPU framebuffer data onto LCD framebuffers. The following constraints apply:&lt;br /&gt;
&lt;br /&gt;
* Output dimensions must not be bigger than input ones.&lt;br /&gt;
* Width dimensions must be &amp;gt;= 64.&lt;br /&gt;
* Height dimensions must be &amp;gt;= 16.&lt;br /&gt;
* Width dimensions are required to be aligned to 16 bytes when doing RGB8 transfers.&lt;br /&gt;
** Otherwise they are required to be aligned to 8 bytes.&lt;br /&gt;
* If downscale is used, input and output dimensions should be the same (otherwise the output is glitched), and width/2 must also follow alignment constraints.&lt;br /&gt;
&lt;br /&gt;
Format conversion results:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Conversion&lt;br /&gt;
!  Result&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Tiled to tiled ===&lt;br /&gt;
&lt;br /&gt;
Officially this is always used with 2x2 downscale, other configurations give glitched output. Hence, this is used for antialiasing and mipmap generation.&lt;br /&gt;
&lt;br /&gt;
The following constraints apply:&lt;br /&gt;
&lt;br /&gt;
* Output dimensions should not be bigger than input ones, otherwise the output is glitched.&lt;br /&gt;
* Width dimensions must be &amp;gt;= 64.&lt;br /&gt;
* Height dimensions must be &amp;gt;= 32.&lt;br /&gt;
&lt;br /&gt;
Format conversion results:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Conversion&lt;br /&gt;
!  Result&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, untested output&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, untested output&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, untested output&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, untested output&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, untested output&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, untested output&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, untested output&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, untested output&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, untested output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, untested output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, untested output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, untested output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, untested output&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== TextureCopy ===&lt;br /&gt;
&lt;br /&gt;
When bit 3 of the control register is set, the hardware performs a TextureCopy-mode transfer: no format conversions are done, instead a raw data copy is performed from the source to the destination, with a configurable gap between lines. All bits of the control register are ignored, except for input/output dimensions, which are used for line width and gap, and bit 2, which must be set when gaps are used.&lt;br /&gt;
&lt;br /&gt;
The total amount of bytes to copy is specified in the size register, the hardware loops reading lines from the input and writing them to the output until this amount is copied. The gap specifies the number of bytes to skip after each line read (a gap of 0 results in a contiguous read). Gaps do not count towards the total size of the transfer.&lt;br /&gt;
&lt;br /&gt;
When setting line width and gap they must be divided by 2 (it can be thought as the calculation being done in bits, and the values being stripped of their lower 4 bits for the alignment). For example, if the left half of a 32x32 RGB8 texture is to be copied, the parameters will be:&lt;br /&gt;
 line width = (16 * 24) &amp;gt;&amp;gt; 4 = 24&lt;br /&gt;
 gap = line width&lt;br /&gt;
 size = 16 * 32 * 3 = 1536&lt;br /&gt;
&lt;br /&gt;
By correctly calculating the input and output gap sizes it is possible to use this functionality to copy arbitrary sub-rectangles between differently-sized framebuffers or textures, which is one of its main uses over a regular no-conversion DisplayTransfer. When copying tiled textures/framebuffers it&#039;s important to remember that the contents of a tile are laid out sequentially in memory, and so this should be taken into account when calculating the transfer parameters.&lt;br /&gt;
&lt;br /&gt;
Specifying invalid/junk values for the TextureCopy dimensions can result in the GPU hanging while attempting to process this TextureCopy. For instance, when in contiguous mode the size must be at least 16; when in gap mode, the size must be at least 192, and the line width must not be 0.&lt;br /&gt;
&lt;br /&gt;
== Command List ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| Buffer size in bytes &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E8&lt;br /&gt;
| Buffer physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018F0&lt;br /&gt;
| Setting bit0 to 1 enables processing GPU command execution. Upon completion, bit0 seems to be reset to 0.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These 3 registers are used by [[GSP_Shared_Memory|GX command]] 1. This is used for [[GPU/Internal_Registers|GPU commands]].&lt;br /&gt;
&lt;br /&gt;
== Framebuffers ==&lt;br /&gt;
These LCD framebuffers normally contain the last rendered frames from the GPU. The framebuffers are drawn from left-to-right, instead of top-to-bottom.(Thus the beginning of the framebuffer is drawn starting at the left side of the screen)&lt;br /&gt;
&lt;br /&gt;
Both of the 3D screen left/right framebuffers are displayed regardless of the 3D slider&#039;s state, however when the 3D slider is set to &amp;quot;off&amp;quot; the 3D effect is disabled. Normally when the 3D slider&#039;s state is set to &amp;quot;off&amp;quot; the left/right framebuffer addresses are set to the same physical address. When the 3D effect is disabled and the left/right framebuffers are set to separate addresses, the LCD seems to alternate between displaying the left/right framebuffer each frame.&lt;br /&gt;
&lt;br /&gt;
==== Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00400 = 0x1C2&lt;br /&gt;
* 0x1EF00404 = 0xD1&lt;br /&gt;
* 0x1EF00408 = 0x1C1&lt;br /&gt;
* 0x1EF0040C = 0x1C1&lt;br /&gt;
* 0x1EF00410 = 0&lt;br /&gt;
* 0x1EF00414 = 0xCF&lt;br /&gt;
* 0x1EF00418 = 0xD1&lt;br /&gt;
* 0x1EF0041C = 0x1C501C1&lt;br /&gt;
* 0x1EF00420 = 0x10000&lt;br /&gt;
* 0x1EF00424 = 0x19D&lt;br /&gt;
* 0x1EF00428 = 2&lt;br /&gt;
* 0x1EF0042C = 0x1C2&lt;br /&gt;
* 0x1EF00430 = 0x1C2&lt;br /&gt;
* 0x1EF00434 = 0x1C2&lt;br /&gt;
* 0x1EF00438 = 1&lt;br /&gt;
* 0x1EF0043C = 2&lt;br /&gt;
* 0x1EF00440 = 0x1960192&lt;br /&gt;
* 0x1EF00444 = 0&lt;br /&gt;
* 0x1EF00448 = 0&lt;br /&gt;
* 0x1EF0045C = 0x19000F0&lt;br /&gt;
* 0x1EF00460 = 0x1c100d1&lt;br /&gt;
* 0x1EF00464 = 0x1920002&lt;br /&gt;
* 0x1EF00470 = 0x80340&lt;br /&gt;
* 0x1EF0049C = 0&lt;br /&gt;
&lt;br /&gt;
==== More Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00468 = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF0046C = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF00494 = 0x18300000&lt;br /&gt;
* 0x1EF00498 = 0x18300000&lt;br /&gt;
* 0x1EF00478 = 1, doesn&#039;t stay 1, read as 0&lt;br /&gt;
* 0x1EF00474 = 0x10501&lt;br /&gt;
&lt;br /&gt;
[[Category:GPU]]&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23615</id>
		<title>GPU/External Registers</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23615"/>
		<updated>2025-07-05T19:28:57Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the address range accessible from the ARM11, used to configure the basic GPU functionality. For information about the internal registers used for 3D rendering, see [[GPU/Internal Registers]].&lt;br /&gt;
&lt;br /&gt;
== Map ==&lt;br /&gt;
Address mappings for the external registers. GSPGPU:WriteHWRegs takes these addresses relative to 0x1EB00000. &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! User VA&lt;br /&gt;
! PA&lt;br /&gt;
! Length&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00000&lt;br /&gt;
| 0x10400000&lt;br /&gt;
| 4&lt;br /&gt;
| Hardware ID&lt;br /&gt;
| Bit2: new model&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00004&lt;br /&gt;
| 0x10400004&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00010&lt;br /&gt;
| 0x10400010&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill1]] &amp;quot;PSC0&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00020&lt;br /&gt;
| 0x10400020&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill2]] &amp;quot;PSC1&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00030&lt;br /&gt;
| 0x10400030&lt;br /&gt;
| 4&lt;br /&gt;
| VRAM bank control&lt;br /&gt;
| Bits 8-11 = bank[i] disabled; other bits are unused.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00034&lt;br /&gt;
| 0x10400034&lt;br /&gt;
| 4&lt;br /&gt;
| GPU Busy&lt;br /&gt;
| Bit26 = PSC0, bit27 = PSC1, Bit30 = PPF, Bit31 = P3D&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00050&lt;br /&gt;
| 0x10400050&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x22221200 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00054&lt;br /&gt;
| 0x10400054&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFF2 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000C0&lt;br /&gt;
| 0x104000C0&lt;br /&gt;
| 4&lt;br /&gt;
| Backlight control&lt;br /&gt;
| Writes 0x0 to allow backlights to turn off, 0x20000000 to force them always on.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00400&lt;br /&gt;
| 0x10400400&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC0&amp;quot; (top screen)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00500&lt;br /&gt;
| 0x10400500&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC1&amp;quot; (bottom)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| 0x10400C00&lt;br /&gt;
| ?&lt;br /&gt;
| [[#Transfer_Engine|Transfer Engine]] &amp;quot;DMA&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|colspan=&amp;quot;5&amp;quot;| 0x1EF01000/0x10401000 - 0x1EF01C00/0x10401C00 maps to [[GPU/Internal_Registers|GPU internal registers]]. These registers are usually not read/written directly here, but are written using the command list interface below (corresponding to the GPUREG_CMDBUF_* internal registers)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01000&lt;br /&gt;
| 0x10401000&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0 on GPU init and before the Command List is used&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01080&lt;br /&gt;
| 0x10401080&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x12345678 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010C0&lt;br /&gt;
| 0x104010C0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFFFFFFF0 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010D0&lt;br /&gt;
| 0x104010D0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 1 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF014??&lt;br /&gt;
| 0x104014??&lt;br /&gt;
| 0x14&lt;br /&gt;
| &amp;quot;PPF&amp;quot; ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| 0x104018E0&lt;br /&gt;
| 0x14&lt;br /&gt;
| [[#Command_List|Command List]] &amp;quot;P3D&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Memory Fill ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  User VA&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X0&lt;br /&gt;
| Buffer start physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X4&lt;br /&gt;
| Buffer end physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X8&lt;br /&gt;
| Fill value&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000XC&lt;br /&gt;
| Control. bit0: start/busy, bit1: finished, bit8-9: fill-width (0=16bit, 1=3=24bit, 2=32bit)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Memory fills are used to initialize buffers in memory with a given value, similar to memset. A memory fill is triggered by setting bit0 in the control register. Doing so aborts any running memory fills on that filling unit. Upon completion, the hardware unsets bit0 and sets bit1 and fires interrupt PSC0.&lt;br /&gt;
&lt;br /&gt;
The addresses must be part of VRAM.&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP Shared Memory#GX SetMemoryFill|GX SetMemoryFill]].&lt;br /&gt;
&lt;br /&gt;
== LCD Source Framebuffer Setup ==&lt;br /&gt;
&lt;br /&gt;
All of these registers must be accessed with 32bit operations regardless of the registers&#039; actual bit size.&lt;br /&gt;
&lt;br /&gt;
The naming of these parameters reflects the physical characteristics of the displays, and not the way the 3DS is normally held.&lt;br /&gt;
&lt;br /&gt;
To make sense of these values, the 3DS must be held in a way, so that the bottom screen is in the left hand, and the top screen is in the right hand, and that way the first pixel will be in the top-left corner, as it should be. If the 3DS is held normally, the first pixel is in the bottom-left corner.&lt;br /&gt;
&lt;br /&gt;
All pixel and scanline timing values are 12bits, unless noted. This also applies to those fields where two u16 are combined into one register. Each u16 field is only 12bits in size. timin&lt;br /&gt;
&lt;br /&gt;
The horizontal timing parameter order is as follows (values may overflow through HTotal register value):&lt;br /&gt;
 0x10 &amp;lt; 0x14 &amp;lt;= 0x60.LO &amp;lt;= 0x04 &amp;lt;= 0x60.HI &amp;lt;= 0x08 &amp;lt;= 0x0C &amp;lt;= 0x10&lt;br /&gt;
 0x18 &amp;lt;= 0x60.LO&lt;br /&gt;
&lt;br /&gt;
Timing starts from HCount == 0, then each absolute value in the beforementioned register chain triggers when HCount == register, latching the primitive display controller into a new mode.&lt;br /&gt;
There is an inherent latch order, where if two simultenaous events occur, one event wins over another.&lt;br /&gt;
&lt;br /&gt;
 Known latched modes (in order):&lt;br /&gt;
 - HSync (triggers a line to the LCD to move to the next line)&lt;br /&gt;
 - Back porch (area between HSync and border being displayed, no pixels pushed, min 16 pixel clocks, otherwise the screen gets glitchy)&lt;br /&gt;
 - Left border start (no image data is being displayed, just a configurable solid color)&lt;br /&gt;
 - Image start (pixel data is being DMA&#039;d from video memory or main RAM)&lt;br /&gt;
 - Right border start/Image end (border color is being displayed after the main image)&lt;br /&gt;
 - Unknown synchronization (supposed to be probably right border end, but this mode seems to be broken or not do anything)&lt;br /&gt;
 - Front porch (no pixels pushed, 68 clock min, otherwise the screen doesn&#039;t sync properly, and really glitches out)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Offset&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| HTotal&lt;br /&gt;
| The total width of a timing scanline. In other words, this is the horizontal refresh clock divider value.&lt;br /&gt;
&lt;br /&gt;
HClock = PClock / (HTotal + 1)&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| HStart&lt;br /&gt;
| Determines when the image is going to be displayed in the visible region (register 0x60).&lt;br /&gt;
|-&lt;br /&gt;
| 0x08&lt;br /&gt;
| HBR&lt;br /&gt;
| Right border start(?). Does nothing.&lt;br /&gt;
&lt;br /&gt;
While this register seems to have no impact on the image whatsoever, it still has to be set to a valid value.&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| 0x0C&lt;br /&gt;
| HPF&lt;br /&gt;
| Front porch. The image is blanked during this period, and no pixels are pushed to the LCD.&lt;br /&gt;
&lt;br /&gt;
Unknown why, but a single dot of red is displayed before entering this mode.&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| HSync&lt;br /&gt;
| Triggers a HSync pulse.&lt;br /&gt;
&lt;br /&gt;
Based on behavior, this needs to last at least a pixel clock for the LCD to register the sync.&lt;br /&gt;
|-&lt;br /&gt;
| 0x14&lt;br /&gt;
| HPB&lt;br /&gt;
| Back porch? Has to be at least one bigger than HSync, otherwise HSync never triggers.&lt;br /&gt;
&lt;br /&gt;
The display is blank, and the LCD displays nothing in this period (doesn&#039;t push pixels).&lt;br /&gt;
|-&lt;br /&gt;
| 0x18&lt;br /&gt;
| HBL&lt;br /&gt;
| Left border trigger treshold. Enables pushing pixels to the display.&lt;br /&gt;
&lt;br /&gt;
If this value is smaller than the back porch, then the back porch period will be zero, and the border will be immediately displayed upon entering the back porch period.&lt;br /&gt;
&lt;br /&gt;
Can be lower than HSync, as the back porch is what takes the controller out of HSync.&lt;br /&gt;
&lt;br /&gt;
Must be &amp;lt;= HDisp start (reg 0x60 low u16), otherwise no pixels will be pushed due to a glitched state.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1C&lt;br /&gt;
| H Interrupt timing&lt;br /&gt;
| Made up from two u16 values, PDC interrupt line is asserted when HCount == low u16, and most likely deasserted when HCount == high u16.&lt;br /&gt;
&lt;br /&gt;
There seems to be some limitations though:&lt;br /&gt;
* low u16 must be smaller than high u16&lt;br /&gt;
* if low u16 is less than HTotal then high u16 must also be smaller than HTotal&lt;br /&gt;
* setting low u16 to &amp;gt;= HTotal disables the interrupt ever firing&lt;br /&gt;
&lt;br /&gt;
This is configured by gsp in a way so that low u16 equals to HTotal, meaning the HSync interrupt will never fire.&lt;br /&gt;
|-&lt;br /&gt;
| 0x20&lt;br /&gt;
| low u16: ???&lt;br /&gt;
high u16: ???&lt;br /&gt;
| ???&lt;br /&gt;
|-&lt;br /&gt;
| 0x24&lt;br /&gt;
| VTotal&lt;br /&gt;
| Total height of the timing window. Can be interpreted as the vertical clock divider.&lt;br /&gt;
&lt;br /&gt;
VClock = PClock / (HTotal + 1) / (VTotal + 1)&lt;br /&gt;
&lt;br /&gt;
Setting this to 494 lowers framerate to about 50.040660858 Hz ((268111856 / 24) / (450 + 1) / (494 + 1)).&lt;br /&gt;
|-&lt;br /&gt;
| 0x28&lt;br /&gt;
| ?&lt;br /&gt;
| Seems to determine the vertical blanking interval.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Setting this to lower than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will cut off the top &amp;lt;code&amp;gt;VTotal - VDisp - thisvalue&amp;lt;/code&amp;gt; lines.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will make the image be pushed downwards with the overscan color visible.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;HTotal&amp;lt;/code&amp;gt; will make the GPU skip vertical pixel data synchronization (hence filling the screen with the rest of the pixel data past the given screen framebuffer size). Also will skip &amp;lt;code&amp;gt;thisvalue + somevalue - HTotal&amp;lt;/code&amp;gt; lines into the &amp;quot;global&amp;quot; pixel buffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x30&lt;br /&gt;
| ?&lt;br /&gt;
| Total amount of vertical scanlines in the pixel buffer, must be bigger than *an unknown blanking-like value*. If this value is less than VDisp then the last two scanlines will be repeated interlaced until VDisp is reached.&lt;br /&gt;
|-&lt;br /&gt;
| 0x34&lt;br /&gt;
| VDisp(?)&lt;br /&gt;
| Total amonut of vertical scanlines displayed (only for top screen it seems like). If this value is less than VTotal then the rest of the scanlines will not be updated on the screen, so those will slowly fade out. Must be bigger than *an unknown blanking-like value*, otherwise an underflow will happen.&lt;br /&gt;
|-&lt;br /&gt;
| 0x38&lt;br /&gt;
| Vertical data offset(?)&lt;br /&gt;
| ??? Seems to offset the screen upwards if this value is high enough. If this value is higher or equal to *some value* (aka. if less than one scanline is displayed on the screen) then the screen will lose synchronization.&lt;br /&gt;
|-&lt;br /&gt;
| 0x40&lt;br /&gt;
| V Interrupt timing&lt;br /&gt;
| Similar to H Interrupt timing (0x1C), except the comparison is done against VCount, the limitations are emposed on VTotal, and the interrupt that fires is VSync.&lt;br /&gt;
&lt;br /&gt;
One important note is that it seems like the VSync interrupt always fires at HCount == 0, and there doesn&#039;t seem to be a register to control this behavior.&lt;br /&gt;
|-&lt;br /&gt;
| 0x44&lt;br /&gt;
| ???&lt;br /&gt;
| similar functionality to 0x10&lt;br /&gt;
|-&lt;br /&gt;
| 0x48&lt;br /&gt;
| ???&lt;br /&gt;
| bit0 seems to disable HSync, bit8 seems to disable VSync, rest of the bits aren&#039;t writable.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4C&lt;br /&gt;
| Overscan filler color&lt;br /&gt;
| 24bits(? top 8bits ignored)&lt;br /&gt;
&lt;br /&gt;
When the visible region is being drawn, but the timing parameters are set up in a way that the framebuffer is smaller than the visible region, it will be filled by this color.&lt;br /&gt;
|-&lt;br /&gt;
| 0x50&lt;br /&gt;
| HCount&lt;br /&gt;
| Horizontal &amp;quot;beam position&amp;quot; counter. Note that this value does not equal to the current pixel being drawn.&lt;br /&gt;
|-&lt;br /&gt;
| 0x54&lt;br /&gt;
| VCount&lt;br /&gt;
| Vertical &amp;quot;beam position&amp;quot; counter. Note that the scanline being drawn isn&#039;t equal to this value.&lt;br /&gt;
|-&lt;br /&gt;
| 0x5C&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: Image width (including some offset?)&lt;br /&gt;
high u16: Image height??? (seems to be unused)&lt;br /&gt;
|-&lt;br /&gt;
| 0x60&lt;br /&gt;
| HDisp&lt;br /&gt;
| low u16: Image start (border --&amp;gt; pixel data)&lt;br /&gt;
high u16: Image end (pixel data --&amp;gt; border)&lt;br /&gt;
|-&lt;br /&gt;
| 0x64&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: unknown&lt;br /&gt;
high u16: framebuffer total height (amount of scanlines blitted regardless of framebuffer height)&lt;br /&gt;
|-&lt;br /&gt;
| 0x68&lt;br /&gt;
| Framebuffer A first address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x6C&lt;br /&gt;
| Framebuffer A second address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x70&lt;br /&gt;
| Framebuffer format and other settings&lt;br /&gt;
| See [[#Framebuffer_format|framebuffer format]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x74&lt;br /&gt;
| PDC control&lt;br /&gt;
| Bit 0: Enable display controller.&lt;br /&gt;
Bit 8: HBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 9: VBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 10: Error IRQ mask? (0 = enabled).&lt;br /&gt;
Bit 16: Output enable?&lt;br /&gt;
|-&lt;br /&gt;
| 0x78&lt;br /&gt;
| Framebuffer select and status&lt;br /&gt;
| Bit 0: Next framebuffer to display (after VBlank).&lt;br /&gt;
Bit 4: Currently displaying framebuffer?&lt;br /&gt;
Bit 8: Reset FIFO?&lt;br /&gt;
Bit 16: HBlank IRQ status/ack. Write 1 to aknowledge.&lt;br /&gt;
Bit 17: VBlank IRQ status/ack.&lt;br /&gt;
Bit 18: Error IRQ status/ack?&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| Color lookup table index select&lt;br /&gt;
| 8bits, write-only&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| Color lookup table indexed element&lt;br /&gt;
| Contains the value of the color lookup table indexed by the above register, 24bits, RGB8 (0x00BBGGRR)  &lt;br /&gt;
Accessing this register will increase the index register by one&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| Framebuffer stride&lt;br /&gt;
| 32bits (bottom 3bits ignored?)&lt;br /&gt;
&lt;br /&gt;
Distance in bytes between the start of two framebuffer rows (must be a multiple of 8).&lt;br /&gt;
&lt;br /&gt;
In other words, this can be interpreted as the amount to add to the framebuffer pointer after displaying a scanline.&lt;br /&gt;
&lt;br /&gt;
Setting this to zero will cause only the first line of the image to be displayed repeated on the entire display. With the HSync interrupt it&#039;s possible to &amp;quot;race the beam&amp;quot; to (ab)use this feature.&lt;br /&gt;
&lt;br /&gt;
Because of this simplicity, writing a negative value here VFlips the image, although that requires the framebuffer pointer register to be set to the start of the last scanline, instead of at the start of the framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| Framebuffer B first address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| Framebuffer B second address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer format ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 2-0&lt;br /&gt;
| [[#Framebuffer_color_formats|Color format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5-4&lt;br /&gt;
| Framebuffer interlacing mode&lt;br /&gt;
&lt;br /&gt;
 0 - A  (no interlacing)&lt;br /&gt;
 1 - AA (scanline doubling)&lt;br /&gt;
 2 - AB (interlace enable)&lt;br /&gt;
 3 - BA (same as above, but the fields are inverted)&lt;br /&gt;
&lt;br /&gt;
In AB and BA interlace modes, a scanline from each framebuffer is output in an alternating manner. In AB mode, Framebuffer A is output on the frist display scanline. Similarly, in BA mode, Framebuffer B gets output to the first display scanline.&lt;br /&gt;
&lt;br /&gt;
The way AB and BA modes work, is that a scanline is output, the framebuffer stride value is added to the internal scanline pointer value, and the other framebuffer is selected. And this alternates until the end of the draw region.&lt;br /&gt;
&lt;br /&gt;
AA interlacing works like AB interlacing, except both internal framebuffer pointers are set to the Framebuffer A pointer value.&lt;br /&gt;
&lt;br /&gt;
In A mode (no interlacing), it doesn&#039;t switch to the other framebuffer at the end of outpuitting a scanline to the display.&lt;br /&gt;
&lt;br /&gt;
Bottom screen has this set to 0 (A mode, no interlacing) at all times.  &lt;br /&gt;
Top screen uses AB interlacing in 3D mode (with 3D slider enabled), and A mode (no interlacing) in 2D mode.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Alternative pixel output mode*&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 9-8&lt;br /&gt;
| DMA size&lt;br /&gt;
&lt;br /&gt;
 0 -  4 FCRAM words (32 bytes)&lt;br /&gt;
 1 -  8 FCRAM words (64 bytes)&lt;br /&gt;
 2 - 16 FCRAM words (128 bytes)&lt;br /&gt;
 3 - ???&lt;br /&gt;
&lt;br /&gt;
FCRAM doesn&#039;t support DMA size 3, as it can only burst up to 16 words (128 bytes), and will show a black screen instead.&lt;br /&gt;
|-&lt;br /&gt;
| 31-16&lt;br /&gt;
| Unknown&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt; The weird thing about bit6, is that it works different between the bottom and top LCD. On the bottom LCD, it doubles the number of outputted pixels (so the same pixel is outputted twice, effectively doing pixel/column doubling). However on the top screen, it does scanline doubling instead.  &lt;br /&gt;
Most likely the top screen receives two pixels at once per clock unit, outputting two scanlines simultaneously.&lt;br /&gt;
&lt;br /&gt;
On a 2DS, it seems to have no effect on the top part of the display, and on the bottom screen it just shifts the framebuffer to the right two pixels.&lt;br /&gt;
&lt;br /&gt;
GSP module only allows the LCD stereoscopy (3D) to be enabled when bit5=1 and bit6=0 here. When GSP module updates this register, GSP module will automatically disable the stereoscopy if those bits are not set for enabling stereoscopy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When both interlacing and alternative mode is disabled (bit6=0), the full resolution of the top screen (240x800) can be utilized if the PDC registers are updated to accomodate this higher resolution. GSP contains tables for this mode (gsp mode == 1). GSP automatically applies this mode if both bit5 and bit6 are cleared. This is also the default, and the only valid mode for the bottom screen in userland.&lt;br /&gt;
&lt;br /&gt;
If only AB interlacing is enabled (bit5=1, bit6=0), gsp detects this as a request to switch to 3D mode (gsp mode == 2), and enables the parallax barrier.  &lt;br /&gt;
It&#039;s unknown how to control this, but some other PDC registers control if interlacing should be done by true interleaving (both framebuffers are treated as 240x400), or by skipping lines (both framebuffers are treated as 240x800).&lt;br /&gt;
&lt;br /&gt;
If only alternative mode is enabled (bit5=0, bit6=1), gsp detects it as a request to switch back to 2D mode for the top screen (gsp mode == 0). This is also the default mode for the top screen.&lt;br /&gt;
&lt;br /&gt;
Both interlacing and scan doubling can&#039;t be enabled in usermode, but it works as expected in baremetal.&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer color formats ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Value&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| GL_RGBA8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| GL_RGB8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| GL_RGB565_OES&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| GL_RGB5_A1_OES&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| GL_RGBA4_OES&lt;br /&gt;
|}&lt;br /&gt;
Color components are laid out in reverse byte order, with the most significant bits used first (i.e. non-24-bit pixels are stored as a little-endian values). For instance, a raw data stream of two GL_RGB565_OES pixels looks like GGGBBBBB RRRRRGGG GGGBBBBB RRRRRGGG.&lt;br /&gt;
&lt;br /&gt;
Color formats 5, 6, and 7 are blocked by gsp, but they behave as pixel-doubled RGBA8 (not line doubling, but instead the same pixel is output twice) if used outside of userland.&lt;br /&gt;
&lt;br /&gt;
== Transfer Engine ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| Input physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C04&lt;br /&gt;
| Output physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C08&lt;br /&gt;
| DisplayTransfer output width (bits 0-15) and height (bits 16-31)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C0C&lt;br /&gt;
| DisplayTransfer input width and height&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C10&lt;br /&gt;
| Transfer flags&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C14&lt;br /&gt;
| ?, GSP writes value 0 here prior to writing to 0x1EF00C18 for DisplayTransfer&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C18&lt;br /&gt;
|  Setting bit0 starts the transfer; upon completion, bit0 is unset and bit8 is set&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C1C&lt;br /&gt;
|  ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C20&lt;br /&gt;
| TextureCopy total amount of data to copy, in bytes&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C24&lt;br /&gt;
| TextureCopy input line width (bits 0-15) and gap (bits 16-31), in 16 byte units&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C28&lt;br /&gt;
| TextureCopy output line width and gap&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Transfer flags:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| When set, the framebuffer data is flipped vertically&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Linear-&amp;gt;tiled mode (overrides tiled-&amp;gt;linear mode)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| This bit is required when the output width is less than the input width for the hardware to properly crop the lines, otherwise the output will be mis-aligned&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| TextureCopy mode (overrides all other modes)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Tiled-&amp;gt;tiled mode (overrides tiled-&amp;gt;linear, linear-&amp;gt;tiled modes)&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 10-8&lt;br /&gt;
| Input [[GPU/External_Registers#Framebuffer_color_formats|color format]]&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 14-12&lt;br /&gt;
| Output color format&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| Use 32x32 block tiling mode, instead of the usual 8x8 one (output dimensions must be multiples of 32, even if cropping with bit 2 set above)&lt;br /&gt;
|-&lt;br /&gt;
| 17-23&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 24-25&lt;br /&gt;
| Scale down the input image using a box filter (0 = No downscale, 1 = 2x1 downscale, 2 = 2x2 downscale, 3 = invalid)&lt;br /&gt;
|-&lt;br /&gt;
| 31-26&lt;br /&gt;
| Not writable&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP_Shared_Memory#Commands|GSP]] for DisplayTransfer and TextureCopy. TextureCopy registers are only used in TextureCopy mode; likewise, DisplayTransfer registers are only used when TextureCopy mode is not set. By default, DisplayTransfer will work in tiled-&amp;gt;linear mode.&lt;br /&gt;
&lt;br /&gt;
=== Tiled to linear ===&lt;br /&gt;
&lt;br /&gt;
Unswizzles the input buffer, this is usually used for transferring GPU framebuffer data onto LCD framebuffers. The following constraints apply:&lt;br /&gt;
&lt;br /&gt;
* Output dimensions must not be bigger than input ones.&lt;br /&gt;
* Width dimensions must be &amp;gt;= 64.&lt;br /&gt;
* Height dimensions must be &amp;gt;= 16.&lt;br /&gt;
* Width dimensions are required to be aligned to 16 bytes when doing RGB8 transfers.&lt;br /&gt;
** Otherwise they are required to be aligned to 8 bytes.&lt;br /&gt;
* If downscale is used, input and output dimensions should be the same (otherwise the output is glitched), and width/2 must also follow alignment constraints.&lt;br /&gt;
&lt;br /&gt;
Format conversion results:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Conversion&lt;br /&gt;
!  Result&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== TextureCopy ===&lt;br /&gt;
&lt;br /&gt;
When bit 3 of the control register is set, the hardware performs a TextureCopy-mode transfer: no format conversions are done, instead a raw data copy is performed from the source to the destination, with a configurable gap between lines. All bits of the control register are ignored, except for input/output dimensions, which are used for line width and gap, and bit 2, which must be set when gaps are used.&lt;br /&gt;
&lt;br /&gt;
The total amount of bytes to copy is specified in the size register, the hardware loops reading lines from the input and writing them to the output until this amount is copied. The gap specifies the number of bytes to skip after each line read (a gap of 0 results in a contiguous read). Gaps do not count towards the total size of the transfer.&lt;br /&gt;
&lt;br /&gt;
When setting line width and gap they must be divided by 2 (it can be thought as the calculation being done in bits, and the values being stripped of their lower 4 bits for the alignment). For example, if the left half of a 32x32 RGB8 texture is to be copied, the parameters will be:&lt;br /&gt;
 line width = (16 * 24) &amp;gt;&amp;gt; 4 = 24&lt;br /&gt;
 gap = line width&lt;br /&gt;
 size = 16 * 32 * 3 = 1536&lt;br /&gt;
&lt;br /&gt;
By correctly calculating the input and output gap sizes it is possible to use this functionality to copy arbitrary sub-rectangles between differently-sized framebuffers or textures, which is one of its main uses over a regular no-conversion DisplayTransfer. When copying tiled textures/framebuffers it&#039;s important to remember that the contents of a tile are laid out sequentially in memory, and so this should be taken into account when calculating the transfer parameters.&lt;br /&gt;
&lt;br /&gt;
Specifying invalid/junk values for the TextureCopy dimensions can result in the GPU hanging while attempting to process this TextureCopy. For instance, when in contiguous mode the size must be at least 16; when in gap mode, the size must be at least 192, and the line width must not be 0.&lt;br /&gt;
&lt;br /&gt;
== Command List ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| Buffer size in bytes &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E8&lt;br /&gt;
| Buffer physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018F0&lt;br /&gt;
| Setting bit0 to 1 enables processing GPU command execution. Upon completion, bit0 seems to be reset to 0.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These 3 registers are used by [[GSP_Shared_Memory|GX command]] 1. This is used for [[GPU/Internal_Registers|GPU commands]].&lt;br /&gt;
&lt;br /&gt;
== Framebuffers ==&lt;br /&gt;
These LCD framebuffers normally contain the last rendered frames from the GPU. The framebuffers are drawn from left-to-right, instead of top-to-bottom.(Thus the beginning of the framebuffer is drawn starting at the left side of the screen)&lt;br /&gt;
&lt;br /&gt;
Both of the 3D screen left/right framebuffers are displayed regardless of the 3D slider&#039;s state, however when the 3D slider is set to &amp;quot;off&amp;quot; the 3D effect is disabled. Normally when the 3D slider&#039;s state is set to &amp;quot;off&amp;quot; the left/right framebuffer addresses are set to the same physical address. When the 3D effect is disabled and the left/right framebuffers are set to separate addresses, the LCD seems to alternate between displaying the left/right framebuffer each frame.&lt;br /&gt;
&lt;br /&gt;
==== Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00400 = 0x1C2&lt;br /&gt;
* 0x1EF00404 = 0xD1&lt;br /&gt;
* 0x1EF00408 = 0x1C1&lt;br /&gt;
* 0x1EF0040C = 0x1C1&lt;br /&gt;
* 0x1EF00410 = 0&lt;br /&gt;
* 0x1EF00414 = 0xCF&lt;br /&gt;
* 0x1EF00418 = 0xD1&lt;br /&gt;
* 0x1EF0041C = 0x1C501C1&lt;br /&gt;
* 0x1EF00420 = 0x10000&lt;br /&gt;
* 0x1EF00424 = 0x19D&lt;br /&gt;
* 0x1EF00428 = 2&lt;br /&gt;
* 0x1EF0042C = 0x1C2&lt;br /&gt;
* 0x1EF00430 = 0x1C2&lt;br /&gt;
* 0x1EF00434 = 0x1C2&lt;br /&gt;
* 0x1EF00438 = 1&lt;br /&gt;
* 0x1EF0043C = 2&lt;br /&gt;
* 0x1EF00440 = 0x1960192&lt;br /&gt;
* 0x1EF00444 = 0&lt;br /&gt;
* 0x1EF00448 = 0&lt;br /&gt;
* 0x1EF0045C = 0x19000F0&lt;br /&gt;
* 0x1EF00460 = 0x1c100d1&lt;br /&gt;
* 0x1EF00464 = 0x1920002&lt;br /&gt;
* 0x1EF00470 = 0x80340&lt;br /&gt;
* 0x1EF0049C = 0&lt;br /&gt;
&lt;br /&gt;
==== More Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00468 = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF0046C = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF00494 = 0x18300000&lt;br /&gt;
* 0x1EF00498 = 0x18300000&lt;br /&gt;
* 0x1EF00478 = 1, doesn&#039;t stay 1, read as 0&lt;br /&gt;
* 0x1EF00474 = 0x10501&lt;br /&gt;
&lt;br /&gt;
[[Category:GPU]]&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=NWM_Services&amp;diff=23589</id>
		<title>NWM Services</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=NWM_Services&amp;diff=23589"/>
		<updated>2025-06-24T22:04:09Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Services]]&lt;br /&gt;
&lt;br /&gt;
These NWM services are used for local-WLAN communications, NWM module handles regular wifi APs as well. These services are used for creating/connecting to networks, and for sending/receiving data over the network etc. NWM module uses the wifi SDIO hardware via the IO registers for this.&lt;br /&gt;
&lt;br /&gt;
=NWM local-WLAN service &amp;quot;nwm::UDS&amp;quot;=&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Command Header&lt;br /&gt;
!  Available since system version&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x000102C2&lt;br /&gt;
| &lt;br /&gt;
| Initialize Deprecated. Appears to be handled about the same way as [[NWMUDS:InitializeWithVersion]], except this uses version=0x100 internally instead of loading it from the command request.&lt;br /&gt;
|-&lt;br /&gt;
| 0x00020000&lt;br /&gt;
| &lt;br /&gt;
| Scrap Not used by sub-wars. This sets a state value to 0x2 then signals an event. This is probably some sort of shutdown command since this state write will result in all UDS commands returning an error if used afterwards.&lt;br /&gt;
|-&lt;br /&gt;
| 0x00030000&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:Finalize|Finalize]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00040402&lt;br /&gt;
| &lt;br /&gt;
| CreateNetwork Deprecated. Only used by very old titles.&lt;br /&gt;
|-&lt;br /&gt;
| 0x00050040&lt;br /&gt;
|&lt;br /&gt;
| [[NWMUDS:EjectClient|EjectClient]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00060000&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:EjectSpectator|EjectSpectator]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00070080&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:UpdateNetworkAttribute|UpdateNetworkAttribute]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00080000&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:DestroyNetwork|DestroyNetwork]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00090442&lt;br /&gt;
| &lt;br /&gt;
| ConnectNetwork Deprecated. Only used by very old titles.&lt;br /&gt;
|-&lt;br /&gt;
| 0x000A0000&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:DisconnectNetwork|DisconnectNetwork]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000B0000&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:GetConnectionStatus|GetConnectionStatus]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000C0000&lt;br /&gt;
| &lt;br /&gt;
| This writes two output u8 values to cmdreply[2] +0/+1. Not used by sub-wars.&lt;br /&gt;
|-&lt;br /&gt;
| 0x000D0040&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:GetNodeInformation|GetNodeInformation]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000E0006&lt;br /&gt;
| &lt;br /&gt;
| &#039;&#039;Identical&#039;&#039; to [[NWMUDS:GetNodeInformationList|GetNodeInformationList]]. Deprecated, only used by old titles.&lt;br /&gt;
|-&lt;br /&gt;
| 0x000F0404&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:StartScan|StartScan]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00100042&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:SetApplicationData|SetApplicationData]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00110040&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:GetApplicationData|GetApplicationData]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00120100&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:Bind|Bind]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00130040&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:Unbind|Unbind]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x001400C0&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:PullPacket|PullPacket]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00150080&lt;br /&gt;
| &lt;br /&gt;
| SetMaxSendDelay(u64 unk) Not used by sub-wars.&lt;br /&gt;
|-&lt;br /&gt;
| 0x00160040&lt;br /&gt;
| &lt;br /&gt;
| (u8 inputval) Unknown. Not used by sub-wars.&lt;br /&gt;
|-&lt;br /&gt;
| 0x00170182&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:SendTo|SendTo]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00180040&lt;br /&gt;
| &lt;br /&gt;
| (u16 inputval) Unknown. Not used by sub-wars.&lt;br /&gt;
|-&lt;br /&gt;
| 0x00190040&lt;br /&gt;
| &lt;br /&gt;
| (u32 inputval) Unknown. Not used by sub-wars.&lt;br /&gt;
|-&lt;br /&gt;
| 0x001A0000&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:GetChannel|GetChannel]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x001B0302&lt;br /&gt;
| &lt;br /&gt;
| [[NWMUDS:InitializeWithVersion|InitializeWithVersion]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x001C0040&lt;br /&gt;
| &lt;br /&gt;
| (u8 inputval) Unknown. Not used by sub-wars.&lt;br /&gt;
|-&lt;br /&gt;
| 0x001D0044&lt;br /&gt;
| Unknown, &amp;gt;[[2.0.0-2]]&lt;br /&gt;
| [[NWMUDS:CreateNetwork2|CreateNetwork2]] This is a replacement for the original network-creation command.&lt;br /&gt;
|-&lt;br /&gt;
| 0x001E0084&lt;br /&gt;
| Unknown, &amp;gt;[[2.0.0-2]]&lt;br /&gt;
| [[NWMUDS:ConnectNetwork2|ConnectNetwork2]] This is a replacement for the original network-connection command.&lt;br /&gt;
|-&lt;br /&gt;
| 0x001F0006&lt;br /&gt;
| Unknown, &amp;gt;[[2.0.0-2]]&lt;br /&gt;
| [[NWMUDS:GetNodeInformationList|GetNodeInformationList]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00200040&lt;br /&gt;
| Unknown, &amp;gt;[[2.0.0-2]]&lt;br /&gt;
| Flush (u8 data_frame_index) Unknown. Not used by sub-wars.&lt;br /&gt;
|-&lt;br /&gt;
| 0x00210080&lt;br /&gt;
| Unknown, &amp;gt;[[2.0.0-2]]&lt;br /&gt;
| [[NWMUDS:SetProbeResponseParam|SetProbeResponseParam]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00220402&lt;br /&gt;
| Unknown, &amp;gt;[[2.0.0-2]]&lt;br /&gt;
| [[NWMUDS:ScanOnConnection|ScanOnConnection]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00230000&lt;br /&gt;
| Unknown, &amp;gt;[[2.0.0-2]]&lt;br /&gt;
| This writes an output u16 value to cmdreply[2]. Unknown. Not used by sub-wars.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
PullPacket is used for receiving data over the network and SendTo is for sending data over the network.&lt;br /&gt;
&lt;br /&gt;
=NWM infrastructure service &amp;quot;nwm::INF&amp;quot;=&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Command Header&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00010000&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00020000&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00030000&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00040000&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00050000&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x000603C4&lt;br /&gt;
| [[NWMINF:RecvBeaconBroadcastData|RecvBeaconBroadcastData]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00070742&lt;br /&gt;
| [[NWMINF:ConnectToEncryptedAP|ConnectToEncryptedAP]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00080302&lt;br /&gt;
| [[NWMINF:ConnectToAP|ConnectToAP]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00090000&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x000A0000&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x000B0000&lt;br /&gt;
| ?, return event handle in cmdbuf[3]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000C0040&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x000D0000&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x000E0002&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x000F0082&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00100040&lt;br /&gt;
| ?&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=NWM socket service &amp;quot;nwm::SOC&amp;quot;=&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Command Header&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00010042&lt;br /&gt;
| (u32 size, &amp;lt;static_buffer translate-hdr with static_buf_id=0&amp;gt;, addr) ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00020080&lt;br /&gt;
| (u32 unk, u32 size) Unknown. Uses size=0x5F8 internally unless the input is &amp;lt;=0x5F8. Uses an ipc static_buffer(static_buf_id=0) for output with the specified size(also used when writing the translate-hdr). Writes an output u32 to cmdreply[2].&lt;br /&gt;
|-&lt;br /&gt;
| 0x00030042&lt;br /&gt;
| (u32 unk, u32 size, &amp;lt;static_buffer translate-hdr with static_buf_id=0&amp;gt;, addr) ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00040042&lt;br /&gt;
| (u32 size, u32 unk, &amp;lt;static_buffer translate-hdr with static_buf_id=1&amp;gt;, addr) ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00050040&lt;br /&gt;
| (u32 unk) ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00060080&lt;br /&gt;
| (u32 unk0, u16 unk1) ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00070040&lt;br /&gt;
| (u16 unk) This writes an output u32 to cmdreply[2].&lt;br /&gt;
|-&lt;br /&gt;
| 0x00080040&lt;br /&gt;
| [[NWMSOC:GetMACAddress|GetMACAddress]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00090000&lt;br /&gt;
| This just copies data from state to the cmdreply, this always returns 0. u32 cmdreply[2] = sharedmem_size, cmdreply[4] = [[NWM_Shared_Memory|sharedmem_handle]], cmdreply[5] = eventhandle.&lt;br /&gt;
|-&lt;br /&gt;
| 0x000A0040&lt;br /&gt;
| (u32 unk) ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x000B0040&lt;br /&gt;
| (u32 unk) This writes an output u32 to cmdreply[2].&lt;br /&gt;
|-&lt;br /&gt;
| 0x000C0040&lt;br /&gt;
| (u32 unk) ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x000D0002&lt;br /&gt;
| ([[IPC|kernel_processid_translatehdr]], u32 processid) ?&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The only sysmodule which uses this is [[Socket_Services|socket]]-sysmodule. The first command used by socket-module is cmd9.&lt;br /&gt;
&lt;br /&gt;
=NWM service &amp;quot;nwm::SAP&amp;quot;=&lt;br /&gt;
&lt;br /&gt;
=NWM local-WLAN [[StreetPass]] service &amp;quot;nwm::CEC&amp;quot;=&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Command Header&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00060002&lt;br /&gt;
| Unknown, called by CECD module, cmdbuf[2] takes an event handle.&lt;br /&gt;
|-&lt;br /&gt;
| 0x000D0082&lt;br /&gt;
| [[NWMCEC:SendProbeRequest|SendProbeRequest]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=NWM service &amp;quot;nwm::EXT&amp;quot;=&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Command Header&lt;br /&gt;
!  Available since system version&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00010000&lt;br /&gt;
| &amp;lt;=[[2.0.0-2]]&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00020000&lt;br /&gt;
| &amp;lt;=[[2.0.0-2]]&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00030000&lt;br /&gt;
| &amp;lt;=[[2.0.0-2]]&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00040040&lt;br /&gt;
| &amp;lt;=[[2.0.0-2]]&lt;br /&gt;
| SetWifiLinkLevel&lt;br /&gt;
|-&lt;br /&gt;
| 0x00050002&lt;br /&gt;
| &amp;lt;=[[2.0.0-2]]&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00060000&lt;br /&gt;
| &amp;lt;=[[2.0.0-2]]&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00070000&lt;br /&gt;
| &amp;lt;=[[2.0.0-2]]&lt;br /&gt;
| This copies 0x1C-bytes from NWM-module state to the data starting at cmdreply[2].&lt;br /&gt;
|-&lt;br /&gt;
| 0x00080040&lt;br /&gt;
| &amp;lt;=[[2.0.0-2]]&lt;br /&gt;
| [[NWMEXT:ControlWirelessEnabled|ControlWirelessEnabled]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00090000&lt;br /&gt;
| &amp;lt;=[[2.0.0-2]]&lt;br /&gt;
| ?&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=NWM service &amp;quot;nwm::TST&amp;quot;=&lt;br /&gt;
&lt;br /&gt;
=BeaconDataReply Structure=&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Max output size, from the command request.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| Total amount of output data written relative to struct+0. 0xC when there&#039;s no entries.&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x4&lt;br /&gt;
| Total entries, 0 for none.&lt;br /&gt;
|-&lt;br /&gt;
| 0xC&lt;br /&gt;
| &amp;lt;Rest of the structure&amp;gt;&lt;br /&gt;
| Beacon entries.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Beacon entry:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| Size of this entire entry. The next entry starts at curentry_startoffset+curentry_size.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x1&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x5&lt;br /&gt;
| 0x1&lt;br /&gt;
| AP wifi channel.&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x1&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x7&lt;br /&gt;
| 0x1&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x6&lt;br /&gt;
| AP MAC address.&lt;br /&gt;
|-&lt;br /&gt;
| 0xE&lt;br /&gt;
| 0x6&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x14&lt;br /&gt;
| 0x4&lt;br /&gt;
| Size of this entire entry, games use this value to traverse the beacons list.&lt;br /&gt;
|-&lt;br /&gt;
| 0x18&lt;br /&gt;
| 0x4&lt;br /&gt;
| Value 0x1C(size of this header and/or offset to the actual beacon data).&lt;br /&gt;
|-&lt;br /&gt;
| 0x1C&lt;br /&gt;
| Entry_size - 0x1C&lt;br /&gt;
| The actual beacon data is located here, starting at the 802.11 management frame header.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This section describes the structure returned by [[NWMINF:RecvBeaconBroadcastData]] and [[NWMUDS:RecvBeaconBroadcastData]].&lt;br /&gt;
&lt;br /&gt;
=ScanInputStruct=&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Two unknown u16s.&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Two unknown u16s.&lt;br /&gt;
|-&lt;br /&gt;
| 2-3&lt;br /&gt;
| Host MAC address. The 6-bytes located here are normally all 0xFF, for all hosts. Otherwise when not set to broadcast-MAC, the command will only return info for the specified host MAC address.&lt;br /&gt;
|-&lt;br /&gt;
| 4-12&lt;br /&gt;
| Uninitialized for UDS.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This section describes the 0x34-byte input structure used by [[NWMINF:RecvBeaconBroadcastData]] and [[NWMUDS:RecvBeaconBroadcastData]].&lt;br /&gt;
&lt;br /&gt;
=Local-WLAN=&lt;br /&gt;
UDS is used for 3DS&amp;lt;&amp;gt;3DS local-WLAN communications, and for 3DS&amp;lt;&amp;gt;Wii U communications. The latter is mainly only used for multi-player in games.&lt;br /&gt;
&lt;br /&gt;
All UDS local-WLAN communications have the CCMP key for data encryption generated via NWM module. The CCMP key passed to nwm::CEC commands(stored in a 0x44-byte input structure) for [[StreetPass]] is generated by the CECD module. The input data used with [[Process_Services|EncryptDecryptAes]] with [[PSPXI:EncryptDecryptAes|keytype1]] is a MD5 hash over the input passphrase. This input passphrase is fixed for [[Download Play]], it&#039;s unique per local-WLAN application. The CTR is a MD5 hash over the below 0x10-byte structure. The output from encrypting that data with AES-CTR is the final CCMP key. This passphrase is a raw input buffer: while the passphrase specified by user-processes is normally a string with the NUL-terminator included, it can be anything(like the [[DLP_Services|WirelessRebootPassphrase]] for example).&lt;br /&gt;
&lt;br /&gt;
The maximum number of nodes(including the host) which can be on an UDS network is 16.&lt;br /&gt;
&lt;br /&gt;
==NodeID==&lt;br /&gt;
There are two types of client connections: regular Client, and Spectator. The latter &#039;&#039;never&#039;&#039; sends &#039;&#039;any&#039;&#039; 802.11 frame at all to the host, hence &#039;&#039;nothing&#039;&#039; actually connected to the network(including the host) can know about any spectators. Once a spectator is &amp;quot;connected&amp;quot; to a network, it can only receive broadcasted data, no sending.&lt;br /&gt;
&lt;br /&gt;
DLP-client connects to the network as a spectator during DLP scanning to get various [[Download_Play|metadata]] including icon data.&lt;br /&gt;
&lt;br /&gt;
===NetworkNodeID===&lt;br /&gt;
This is the network u16 ID for each device on the UDS network. NodeID 0xFFFF is a broadcast alias. 0x1 is for the host, the 0x2 for the first client, 0x3 for the second client, and so on.&lt;br /&gt;
&lt;br /&gt;
The spectator doesn&#039;t have a NetworkNodeID, since it can&#039;t [[NWMUDS:SendTo|send]] any data.&lt;br /&gt;
&lt;br /&gt;
NetworkNodeIDs for clients do not change when any clients disconnect, likewise for the encrypted node-listing stored in the wifi beacons. When a client disconnects, the corresponding NetworkNodeID bit in the [[NWMUDS:GetConnectionStatus|node_bitmask]] is cleared. When a client is connecting, the client is assigned the NetworkNodeID with the lowest corresponding clear-bit in the [[NWMUDS:GetConnectionStatus|node_bitmask]], then that bit is set.&lt;br /&gt;
&lt;br /&gt;
===BindNodeID===&lt;br /&gt;
This u32 is an ID only used on the local device. How many devices are on the network or which device this system is does not affect this ID.&lt;br /&gt;
&lt;br /&gt;
The spectator uses BindNodeID 0x1. DLP uses BindNodeID 0x3 when connecting as an actual client. Hence, it seems BindNodeID bit0 is spectator-related. All normal nodes(host/client) start with BindNodeID 0x2. When connecting to a network again(and probably with network creation) without reinitializing NWMUDS, official user processes increase the used BindNodeID by 0x2.&lt;br /&gt;
&lt;br /&gt;
BindNodeID value 0x0 is invalid. The maximum number of BindNodeIDs which can be open at the same time is 16.&lt;br /&gt;
&lt;br /&gt;
==Application data transfer==&lt;br /&gt;
The protocol used for sending/receiving data over the network with UDS by official applications is [[PRUDP]](in some cases at least). Mario Kart 7 uses PRUDP here. Triforce Heroes uses plaintext for whatever protocol it uses for UDS.&lt;br /&gt;
&lt;br /&gt;
The UDS version of [[PRUDP]] is different from the normal UDP version it appears(no afa1/a1af data for example).&lt;br /&gt;
&lt;br /&gt;
==Communication protocol==&lt;br /&gt;
The process of connecting to a host and exchanging data follows the sequence:&lt;br /&gt;
&lt;br /&gt;
Client-&amp;gt;Server: Authentication frame SEQ1&lt;br /&gt;
&lt;br /&gt;
Server-&amp;gt;Client: Authentication frame SEQ2&lt;br /&gt;
&lt;br /&gt;
[There does not appear to be an association request frame sent by the client to the server, it is however possible that it was sent and just not captured by the test equipment]&lt;br /&gt;
&lt;br /&gt;
Server-&amp;gt;Client: Association Response frame with association id&lt;br /&gt;
&lt;br /&gt;
[From here on, the client is connected to the server]&lt;br /&gt;
&lt;br /&gt;
Client-&amp;gt;Server: Encrypted data packet containing an 8-byte 802.2 LLC header with ethertype = EAPoL (0x888E) and an u16 header of 0x201 ([[NWM_Services#EAPoL-Start frame|EAPoL-Start]])&lt;br /&gt;
&lt;br /&gt;
Server-&amp;gt;Broadcast: Encrypted data packet containing the updated node information after the client connected (Using ethertype = SecureData).&lt;br /&gt;
&lt;br /&gt;
Server-&amp;gt;Client: Encrypted data packet containing an 8-byte 802.2 LLC header with ethertype = EAPoL (0x888E) and an u16 header of 0x0202([[NWM_Services#EAPoL-Logoff frame|EAPoL-Logoff]])&lt;br /&gt;
&lt;br /&gt;
[From here on, data packets sent using SendTo are encapsulated with an LLC header with ethertype = 0x876D ([[NWM_Services#SecureData NWM header|SecureData]])]&lt;br /&gt;
&lt;br /&gt;
[The client also sends periodic SecureData data frames on its own, these are probably ping frames]&lt;br /&gt;
&lt;br /&gt;
==Data frames==&lt;br /&gt;
Data is transferred over the network using [[NWMUDS:PullPacket]]/[[NWMUDS:SendTo]]. That data is transferred using 802.11 data frames using CCMP encryption. The encrypted data contained in the frame starts with the 0x8-byte [https://en.wikipedia.org/wiki/Subnetwork_Access_Protocol#Use LLC header], then the 0xE-byte NWM header, followed by the actual application data from the previously mentioned commands. When [[NWMUDS:SendTo]] was used with dst_NodeID = broadcast, the data frame is sent to the 802.11 broadcast MAC address. Otherwise with a specific NodeID, the data frame is sent to the actual MAC address for that device.&lt;br /&gt;
&lt;br /&gt;
Official application data is normally stored here as big-endian.&lt;br /&gt;
&lt;br /&gt;
Application data packets are sent to the host using 802.11 unicast, which then acts as a router and forwards the packet to the right destination node id based on the SecureData header using either broadcast or unicast, it is not yet known how it chooses which to use.&lt;br /&gt;
&lt;br /&gt;
==Special data channels==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Channel&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x101&lt;br /&gt;
| Used when broadcasting the updated node information after a new client connects.&lt;br /&gt;
|-&lt;br /&gt;
| 0x103&lt;br /&gt;
| Used when sending what appears to be &amp;quot;ping&amp;quot; or &amp;quot;null&amp;quot; frames.&lt;br /&gt;
|-&lt;br /&gt;
| 0x104&lt;br /&gt;
| Used to signal the ejection of all spectators in the network.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==SecureData NWM header==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x2&lt;br /&gt;
| Size of the entire frame minus the 8 bytes from the LLC header.&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| 0x2&lt;br /&gt;
| unknown&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x2&lt;br /&gt;
| Size of the entire frame minus 12 bytes.&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x2&lt;br /&gt;
| Data channel. Applications can only use the low 8 bits, channels greater than 255 are reserved for management functions.&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x2&lt;br /&gt;
| Sequence number, incremented after each sent packet.&lt;br /&gt;
|-&lt;br /&gt;
| 0xA&lt;br /&gt;
| 0x2&lt;br /&gt;
| Destination network node id&lt;br /&gt;
|-&lt;br /&gt;
| 0xC&lt;br /&gt;
| 0x2&lt;br /&gt;
| Source network node id&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This data is stored as big-endian.&lt;br /&gt;
&lt;br /&gt;
==EAPoL-Start frame==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x2&lt;br /&gt;
| Always 0x201 in big-endian.&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| 0x2&lt;br /&gt;
| Association id of the sending client in big-endian.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x2&lt;br /&gt;
| Unknown. Always 0x1 in big-endian. The parser for this packet errors out if this is &amp;gt; 3.&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x2&lt;br /&gt;
| Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x28&lt;br /&gt;
| NodeInfo structure with all fields in big-endian&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This data is stored as big-endian.&lt;br /&gt;
&lt;br /&gt;
==EAPoL-Logoff frame==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x2&lt;br /&gt;
| Always 0x202 in big-endian.&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| 0x2&lt;br /&gt;
| Unknown. Always 0?&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x2&lt;br /&gt;
| Assigned network node id.&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x6&lt;br /&gt;
| Mac address of the newly connected client.&lt;br /&gt;
|-&lt;br /&gt;
| 0xC&lt;br /&gt;
| 0x4&lt;br /&gt;
| Unknown&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| 0x2&lt;br /&gt;
| Unknown. Always 0.&lt;br /&gt;
|-&lt;br /&gt;
| 0x12&lt;br /&gt;
| 0x1&lt;br /&gt;
| Number of connected nodes, including the new client.&lt;br /&gt;
|-&lt;br /&gt;
| 0x13&lt;br /&gt;
| 0x1&lt;br /&gt;
| Max number of nodes.&lt;br /&gt;
|-&lt;br /&gt;
| 0x14&lt;br /&gt;
| 0x2&lt;br /&gt;
| Always 0.&lt;br /&gt;
|-&lt;br /&gt;
| 0x16&lt;br /&gt;
| 0x2&lt;br /&gt;
| Unknown.&lt;br /&gt;
|-&lt;br /&gt;
| 0x18&lt;br /&gt;
| 0x280&lt;br /&gt;
| List of 16 NodeInfo structures with all fields in big-endian&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This data is stored as big-endian.&lt;br /&gt;
&lt;br /&gt;
==Structure used for generating the CTR for CCMP key generation==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x4&lt;br /&gt;
| wlancommID&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x4&lt;br /&gt;
| networkID&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x6&lt;br /&gt;
| Host MAC address.&lt;br /&gt;
|-&lt;br /&gt;
| 0xE&lt;br /&gt;
| 0x2&lt;br /&gt;
| id8&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This data is stored as little-endian.&lt;br /&gt;
&lt;br /&gt;
==CTR used for beacon tags crypto==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x6&lt;br /&gt;
| Host MAC address&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x4&lt;br /&gt;
| wlancommID&lt;br /&gt;
|-&lt;br /&gt;
| 0xA&lt;br /&gt;
| 0x1&lt;br /&gt;
| id8&lt;br /&gt;
|-&lt;br /&gt;
| 0xB&lt;br /&gt;
| 0x1&lt;br /&gt;
| Padding, value zero.&lt;br /&gt;
|-&lt;br /&gt;
| 0xC&lt;br /&gt;
| 0x4&lt;br /&gt;
| networkID&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This data is stored as little-endian. All data here is all-zero except for the MAC address, when the u8 at offset 0x8 in the network-struct is 0.&lt;br /&gt;
&lt;br /&gt;
==Network structure==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x6&lt;br /&gt;
| This is the MAC address of the host. This is used for when [[NWMUDS:ConnectToNetwork|connecting]] to the network.&lt;br /&gt;
|-&lt;br /&gt;
| 0x6&lt;br /&gt;
| 0x1&lt;br /&gt;
| This is actually written as an u16 without byte-swapping. This is the network wifi channel. When connecting this is normally non-zero. When hosting, this can be 0 to automatically select a channel, otherwise the specified channel is used. When non-zero official user-processes require this value to be one of the following when hosting: 1, 6, or 11.&lt;br /&gt;
|-&lt;br /&gt;
| 0x7&lt;br /&gt;
| 0x1&lt;br /&gt;
| Padding&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x1&lt;br /&gt;
| Initialized flag. Must be non-zero otherwise NWM-module will use value 0x0 for most/all(?) fields in this structure when reading these fields.&lt;br /&gt;
|-&lt;br /&gt;
| 0xC&lt;br /&gt;
| 0x3&lt;br /&gt;
| This is the OUI value for use with the beacon tags. Normally this is 001F32.&lt;br /&gt;
|-&lt;br /&gt;
| 0xF&lt;br /&gt;
| 0x1&lt;br /&gt;
| OUI type (21/0x15)&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| 0x4&lt;br /&gt;
| wlancommID. Local-WLAN communication ID, normally this is: (user_process [[Title_list|uniqueID]] &amp;lt;&amp;lt; 8) | val. Where val is 0x10 on retail([[Configuration_Memory#ENVINFO|ENVINFO]] bit0 set), 0x90 for devunit. Official software includes an input bool flag parameter for setting bit0 in this wlancommID, normally that flag isn&#039;t set. For [[Download Play]], this is always 0x2810 on retail(0x2890 on devunit).&lt;br /&gt;
&lt;br /&gt;
This wlancommID can have the side affect of region-locking when the title uses the uniqueID for the current title(hard-coded in .text normally), instead of using a fixed input uniqueID for each region of the title.&lt;br /&gt;
|-&lt;br /&gt;
| 0x14&lt;br /&gt;
| 0x1&lt;br /&gt;
| id8. ID, for [[Download Play]] this is 0x55. 0x55/&#039;U&#039; seems to be used for networks where Wii U can host it(Download Play, Smash Bros, ...) - this value isn&#039;t known to be actually checked anywhere however.&lt;br /&gt;
|-&lt;br /&gt;
| 0x15&lt;br /&gt;
| 0x1&lt;br /&gt;
| Number of times the network structure hash was updated.&lt;br /&gt;
|-&lt;br /&gt;
| 0x16&lt;br /&gt;
| 0x2&lt;br /&gt;
| This network attributes u16 bitmask can be written via [[NWMUDS:UpdateNetworkAttribute]].&lt;br /&gt;
Bitmasks:&lt;br /&gt;
* 0x1: When set, spectators are not allowed to connect(see [[NWMUDS:EjectSpectator|here]]). Checked by official user-processes before using [[NWMUDS:ConnectToNetwork]], when connecting as a Spectator. Must be clear otherwise that code returns error 0xE10113EA. If the initialized_flag at offset 0x8 is zero, this code handles it the same way as if this bit was set. The latest NWM-module handles checking this bit itself however.&lt;br /&gt;
* 0x2: When set, new regular-clients are not allowed to connect.&lt;br /&gt;
* 0x4: Unknown, has no affect on new clients/spectators connecting. Official software has an option for setting this bit via an input flag from the same code using bitmask 0x2. Official software always clears bitmask 0x6 when unblocking new connections.&lt;br /&gt;
|-&lt;br /&gt;
| 0x18&lt;br /&gt;
| 0x4&lt;br /&gt;
| u32 networkID, randomly-generated when creating the network. The network SSID used when a client connects to the network is sprintf(out, &amp;quot;%08X&amp;quot;, networkID).&lt;br /&gt;
|-&lt;br /&gt;
| 0x1C&lt;br /&gt;
| 0x1&lt;br /&gt;
| Total number of currently connected nodes, including the host.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1D&lt;br /&gt;
| 0x1&lt;br /&gt;
| Maximum number of nodes, including the host. This also is the total number of entries stored under the array in the encrypted beacon data.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1E&lt;br /&gt;
| 0xD&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x2B&lt;br /&gt;
| 0x14&lt;br /&gt;
| SHA1 hash of the network structure, starting at the OUI field (offset 0xC) and spanning SizeOfAppData + 0x34. The unused space of the app data buffer is not hashed.&lt;br /&gt;
|-&lt;br /&gt;
| 0x3F&lt;br /&gt;
| 0x1&lt;br /&gt;
| Size of appdata.&lt;br /&gt;
|-&lt;br /&gt;
| 0x40&lt;br /&gt;
| 0xC8&lt;br /&gt;
| Appdata(Application data), if any. Size of the appdata is specified via the u8 at offset 0x3F. This data is not used when the size-field is zero.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This 0x108-byte structure is used for [[NWMUDS:BeginHostingNetwork]], [[NWMUDS:ConnectToNetwork]], etc. This data is stored as big-endian.&lt;br /&gt;
&lt;br /&gt;
==NodeInfo structure==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x8&lt;br /&gt;
| u64 ID, this is the UDS version of the FriendCodeSeed. This is loaded from BlkID 0x00090000 in the [[Config_Savegame|system-config]] via [[CfgS:GetConfigInfoBlk2]].&lt;br /&gt;
|-&lt;br /&gt;
| 0x8&lt;br /&gt;
| 0x14&lt;br /&gt;
| The first 0x18-bytes from BlkID 0x000A0000 in the [[Config_Savegame|system-config]] loaded via [[CfgS:GetConfigInfoBlk2]] is written here by user-processes. However, the data at +0x14(absolute offset 0x1C) is written by NWM-module later.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1C&lt;br /&gt;
| 0x2&lt;br /&gt;
| u16, unknown. Set to 0x0 with the output from [[NWMUDS:DecryptBeaconData]].&lt;br /&gt;
|-&lt;br /&gt;
| 0x1E&lt;br /&gt;
| 0x1&lt;br /&gt;
| u8 flag, unknown. Originates from the u16 bitmask in the beacon node-list header. This flag is normally 0 since that bitmask is normally 0?&lt;br /&gt;
|-&lt;br /&gt;
| 0x1F&lt;br /&gt;
| 0x1&lt;br /&gt;
| Padding?&lt;br /&gt;
|-&lt;br /&gt;
| 0x20&lt;br /&gt;
| 0x2&lt;br /&gt;
| u16 NetworkNodeID&lt;br /&gt;
|-&lt;br /&gt;
| 0x22&lt;br /&gt;
| 0x6&lt;br /&gt;
| Normally zero?&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The first 0x20-bytes are written by the user-process before using this structure with [[NWMUDS:InitializeWithVersion]]. The data starting at offset 0x8 is only initialized by NWM-module.&lt;br /&gt;
&lt;br /&gt;
== UDS Beacons ==&lt;br /&gt;
The UDS host broadcasts a beacon containing at least two Nintendo-vendor tags(tag number 0xDD, see above for the OUI), normally the data stored in these tags are static. The second tag contains the big-endian u32 networkID, used by the clients when connecting to the host and for the above CCMP key generation. The Nintendo-vendor tag(s) following the first two are unique to the process using UDS, these tags are used for broadcasting metadata regarding the host.&lt;br /&gt;
&lt;br /&gt;
A tool for these beacons is available here: [https://github.com/yellows8/ctr-wlanbeacontool]&lt;br /&gt;
&lt;br /&gt;
=== UDS Beacon Tags ===&lt;br /&gt;
The following is the structure of each tag, starting at the OUI. The order of the tags is the same as listed below. All data stored under these tags are stored as big-endian.&lt;br /&gt;
&lt;br /&gt;
==== OUI Type 20 ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x3&lt;br /&gt;
| OUI, see above.&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| 0x1&lt;br /&gt;
| OUI type (20/0x14)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| 0x3&lt;br /&gt;
| Sample data: 0a 00 00&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Normally the size of this tag(from the tag size field) is 0x07.&lt;br /&gt;
&lt;br /&gt;
==== OUI Type 21 ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x1F&lt;br /&gt;
| This is the network structure starting at offset 0xC, with the first 0x1F-bytes from there.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1F&lt;br /&gt;
| 0x14&lt;br /&gt;
| SHA1 hash. When doing the hashing, this hash is cleared to zero. The hash data starts at offset 0x0(OUI), and the size is 0x34 + &amp;lt;value of the u8 at offset 0x33&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| 0x33&lt;br /&gt;
| 0x1&lt;br /&gt;
| Size of appdata. Normally zero. When non-zero this appdata is located at offset 0x34.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Normally the size of this tag(from the tag size field) is 0x34, not including appdata.&lt;br /&gt;
&lt;br /&gt;
==== OUI Type 24 ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x3&lt;br /&gt;
| OUI, see above.&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| 0x1&lt;br /&gt;
| OUI type (24/0x18)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| See below&lt;br /&gt;
| Encrypted data&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This is the tag0 used with [[NWMUDS:DecryptBeaconData]]. The size of data stored under this tag has a maximum size of 0xFA-bytes, however normally the size is smaller than that. Additional encrypted data, if any, is stored under the below tag1.&lt;br /&gt;
&lt;br /&gt;
==== OUI Type 25 ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x3&lt;br /&gt;
| OUI, see above.&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| 0x1&lt;br /&gt;
| OUI type (25/0x19)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4&lt;br /&gt;
| See above&lt;br /&gt;
| Encrypted data&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When this exists in the beacon, this is the tag1 used with [[NWMUDS:DecryptBeaconData]]. The data stored here is the 0xFA-bytes following the previous encrypted data in tag0, for more space for storing the encrypted data.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Encrypted beacon data ====&lt;br /&gt;
The following structure is for the plaintext version of the encrypted data, stored as big-endian.&lt;br /&gt;
&lt;br /&gt;
This data is encrypted with AES-CTR, by NWM module in software. The AES key is stored in NWM module itself. See above for the CTR. The size of this encrypted data is 0x12 + (0x1E*val), where val is the u8 from networkstruct offset 0x1D.&lt;br /&gt;
&lt;br /&gt;
===== Structure =====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x10&lt;br /&gt;
| MD5 over the rest of the data following this(plaintext).&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| 0x2&lt;br /&gt;
| u16 bitmask. Unknown, normally 0? Bit0 is for entry0, bit1 for entry1, and so on.&lt;br /&gt;
|-&lt;br /&gt;
| 0x12&lt;br /&gt;
| 0x1E * &amp;lt;total array entries&amp;gt;&lt;br /&gt;
| This is an array of entries for each of the devices on this network, the first entry is for the host and the rest is for the client(s).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===== Array entry =====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| 0x1C&lt;br /&gt;
| This is the first 0x1C-bytes of the NodeInfo structure, stored as big-endian.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1C&lt;br /&gt;
| 0x2&lt;br /&gt;
| u16 NetworkNodeID, stored as big-endian.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Each entry is for a node.&lt;br /&gt;
&lt;br /&gt;
= Mapped IO =&lt;br /&gt;
&#039;&#039;All&#039;&#039; of the [[IO_Registers|IO]] mapped under the NWM-module process is listed below:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Userland address&lt;br /&gt;
!  Physical address&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EC22000&lt;br /&gt;
| 0x10122000&lt;br /&gt;
| 0x1000&lt;br /&gt;
| [[WIFI_Registers]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EC40000&lt;br /&gt;
| 0x10140000&lt;br /&gt;
| 0x1000&lt;br /&gt;
| [[PDN_Registers]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EE22000&lt;br /&gt;
| 0x10322000&lt;br /&gt;
| 0x1000&lt;br /&gt;
| &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=Errors=&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Error code&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0xC8A06C0D&lt;br /&gt;
| The operation being performed is already done (e.g., if you run NWMEXT_ControlWirelessEnabled to turn wifi on when it&#039;s on already, you can&#039;t turn it on again).&lt;br /&gt;
|-&lt;br /&gt;
| 0xC8A113EA&lt;br /&gt;
| Returned when the command isn&#039;t allowed to be used on this device.&lt;br /&gt;
|-&lt;br /&gt;
| 0xC90113FA&lt;br /&gt;
| Node doesn&#039;t exist / invalid NetworkNodeID?&lt;br /&gt;
|-&lt;br /&gt;
| 0xC92113FB&lt;br /&gt;
| Returned when trying to connect to a host when the host has the specified connection-type blocked via the network attributes. There might be other causes too.&lt;br /&gt;
|-&lt;br /&gt;
| 0xE10113E9&lt;br /&gt;
| Returned when the input size is invalid. Returned by [[NWMUDS:PullPacket]] when the input size is smaller than the frame_size.&lt;br /&gt;
|-&lt;br /&gt;
| 0xE10113EA&lt;br /&gt;
| Invalid bind / data_channel is invalid(0x0).&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=3DS_System_Flaws&amp;diff=23588</id>
		<title>3DS System Flaws</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=3DS_System_Flaws&amp;diff=23588"/>
		<updated>2025-06-24T12:48:16Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: GSP DMA + RO stuff&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Exploits are used to execute unofficial code (homebrew) on the Nintendo 3DS. This page is a list of publicly known system flaws, for userland applications/applets flaws see [[3DS_Userland_Flaws|here]].&lt;br /&gt;
&lt;br /&gt;
=Stale / Rejected Efforts=&lt;br /&gt;
* In the early days of 3DS hacking, Neimod was working on a RAM dumping setup for a while. He has de-soldered the 3DS&#039;s RAM chip and hooked it and the RAM pinouts on the 3DS&#039;s PCB up to a custom RAM dumping setup. He &#039;&#039;has&#039;&#039; published photos showing his setup to be working quite well, with the 3DS successfully booting up, but however, his flickr stream is now private along with most of his work and this method has been unreleased. RAM dumping can be done through homebrew now, making this method obsolete regardless.&lt;br /&gt;
&lt;br /&gt;
==Tips and info==&lt;br /&gt;
The 3DS uses the XN feature of the ARM11 processor. There&#039;s no official way from applications to enable executable permission for memory containing arbitrary unsigned code(there&#039;s a [[SVC]] for this, but only [[RO_Services|RO-module]] has access to it). A usable userland exploit would still be useful: you could only do return-oriented-programming with it initially. From ROP one could then exploit system flaw(s), see below.&lt;br /&gt;
&lt;br /&gt;
SD card [[extdata]] and SD savegames can be attacked, for consoles where the console-unique [[Nand/private/movable.sed|movable.sed]] was dumped(accessing SD data is far easier by running code on the target 3DS however).&lt;br /&gt;
&lt;br /&gt;
=System flaws=&lt;br /&gt;
== Hardware ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Fixed with hardware model/revision&lt;br /&gt;
!  Newest hardware model/revision this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| ARM9/ARM11 bootrom vectors point at uninitialized RAM&lt;br /&gt;
| ARM9&#039;s and ARM11&#039;s exception vectors are hardcoded to point at the CPU&#039;s internal memory (0x08000000 region for ARM9, AXIWRAM for ARM11). While the bootrom does set them up to point to an endless loop at some point during boot, it does not do so immediately. As such, a carefully-timed fault injection (via hardware) to trigger an exception (such as an invalid instruction) will cause execution to fall into ARM9 RAM. &lt;br /&gt;
Since RAM isn&#039;t cleared on boot (see below), one can immediately start execution of their own code here to dump bootrom, OTP, etc.&lt;br /&gt;
The ARM9 bootrom does the following at reset:  reset vector branches to another instruction, then branches to bootrom+0x8000. Hence, there&#039;s no way to know for certain when exactly the ARM9 exception-vector data stored in memory gets initialized.&lt;br /&gt;
&lt;br /&gt;
The vulnerable timing range is about 100 CPU cycles after they start (which happens after the PLLs have stabilized after power-up). A glitch needs to be injected during one of these 100 cycles for the attack to succeed.&lt;br /&gt;
&lt;br /&gt;
It has been exploited by derrek to dump the ARM9 bootrom as of Summer 2015.&lt;br /&gt;
| None: all available 3DS models at the time of writing have the exact same ARM9/ARM11 bootrom for the unprotected areas.&lt;br /&gt;
| New3DS&lt;br /&gt;
| End of February 2014&lt;br /&gt;
| [[User:Derrek|derrek]], WulfyStylez (May 2015) independently&lt;br /&gt;
|-&lt;br /&gt;
| Missing AES key clearing&lt;br /&gt;
| The hardware AES engine does not clear keys when doing a hard reset/reboot.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| August 2014&lt;br /&gt;
| Mathieulh/Others&lt;br /&gt;
|-&lt;br /&gt;
| No RAM clearing on reboots&lt;br /&gt;
| On an MCU-triggered reboot all RAM including FCRAM/ARM9 memory/AXIWRAM/VRAM keeps its contents.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| March 2014&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| 32bits of actual console-unique TWLNAND keydata&lt;br /&gt;
| On retail the 8-bytes at ARM9 address [[Memory_layout|0x01FFB808]] are XORed with hard-coded data, to generate the TWL console-unique keys, including TWLNAND. On Old3DS the high u32 is always 0x0, while on New3DS that u32 is always 0x2. On top of this, the lower u32&#039;s highest bit is always ORed. only 31 bits of the TWL console-unique keydata / TWL consoleID are actually console-unique.&lt;br /&gt;
This allows one to easily bruteforce the TWL console-unique keydata with *just* data from TWLNAND. On DSi the actual console-unique data for key generation is 8-bytes(all bytes actually set).&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| 2012?&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| DSi / 3DS-TWL key-generator&lt;br /&gt;
| After using the key generator to generate the normal-key, you could overwrite parts of the normal-key with your own data and then recover the key-generator output by comparing the new crypto output with the original crypto output. From the normal-key outputs, you could deduce the TWL key-generator function.&lt;br /&gt;
This applies to the keyX/keyY too.&lt;br /&gt;
&lt;br /&gt;
This attack does not work for the 3DS key-generator because keyslots 0-3 are only for TWL keys.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| 2011&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| 3DS key-generator&lt;br /&gt;
| The algorithm for generating the normal-keys for keyslots is cryptographically weak.  As a result, it is easily susceptible to differential cryptanalysis if the normal-key corresponding to any scrambler-generated keyslot is discovered.&lt;br /&gt;
&lt;br /&gt;
Several such pairs of matching normal-keys and KeyY values were found, leading to deducing the key-generator function.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| February 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]], [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| RSA keyslots don&#039;t clear exponent when setting modulus&lt;br /&gt;
| The [[RSA_Registers|RSA keyslots]] are set by boot ROM to have four private RSA keys.  The exponent value in the RSA registers is write-only and not readable.&lt;br /&gt;
&lt;br /&gt;
However, when setting a keyslot&#039;s modulus, the RSA hardware leaves the exponent alone.  This allows retrieving the exponent by doing a discrete logarithm of the output.&lt;br /&gt;
&lt;br /&gt;
By setting the modulus to a prime number whose modular multiplicative order is &amp;quot;smooth&amp;quot; (that is, p-1 is divisible by only small prime numbers), discrete logarithms can be calculated quickly using the [[wikipedia:Pohlig-Hellman algorithm|Pohlig-Hellman algorithm]].  If the prime chosen is greater than the modulus, but the same bit size, the discrete logarithm is the private exponent.&lt;br /&gt;
&lt;br /&gt;
This exploit&#039;s usefulness is limited: RSA keyslot 0 is only used in current firmware for deriving the 6.x save and 7.x NCCH keys, which were already known, and the other three keyslots are entirely unused.  Additionally, with a boot ROM dump, this exploit is moot; these private keys are located in the protected ARM9 boot ROM.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| March 2016&lt;br /&gt;
| [[User:Myria|Myria]]&lt;br /&gt;
|-&lt;br /&gt;
| [[CONFIG11_Registers#CFG11_GPUPROT|CFG11_GPUPROT]] allowing acccess to AXIWRAM/FCRAM-BASE-memregion&lt;br /&gt;
| [[CONFIG11_Registers#CFG11_GPUPROT|CFG11_GPUPROT]] can be configured by anything with access to it to allow the GPU to access the entire AXIWRAM+FCRAM. For example, this is an issue for any sysmodule that gets exploited and has access to this register memory-page(include one that&#039;s listed below).&lt;br /&gt;
&lt;br /&gt;
See also &amp;quot;kernelhax via gspwn&amp;quot; below.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| February 7, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Boot ROM ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Fixed with hardware model/revision&lt;br /&gt;
!  Newest hardware model/revision this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| FIRM partitions known-plaintext&lt;br /&gt;
| The [[Flash_Filesystem|FIRM partitions]] are encrypted with AES-CTR without a MAC. Since this works by XOR&#039;ing data with a static (per-console in this case) keystream, one can deduce the keystream of a portion of each FIRM partition if they have the actual FIRM binary stored in it.&lt;br /&gt;
&lt;br /&gt;
This can be paired with many exploits. For example, it allows minor FIRM downgrades (i.e. 10.4 to 9.6 or 9.5 to 9.4, but not 9.6 to 9.5).&lt;br /&gt;
However it is most commonly used to install arbitrary FIRMs (usually boot9strap), thanks to sighax.&lt;br /&gt;
&lt;br /&gt;
This can be somewhat addressed by having a FIRM header skip over previously used section offsets, but this would just air-gap newer FIRMs without fixing the core bug. This can also only be done a limited number of times due to the size of FIRM versus the size of the partitions.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| &lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| Boot9 AES keyinit function issues&lt;br /&gt;
| [[Bootloader|Boot9]] seems to have two bugs in the AES key-init function, see [[AES_Registers#AES_key-init|here]].&lt;br /&gt;
| None&lt;br /&gt;
| BootROM issue.&lt;br /&gt;
| 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| New3DS has same boot ROM as Old3DS&lt;br /&gt;
| The New3DS has the exact same boot ROM as the Old3DS.  This means, among other things, that all the same boot ROM flaws are present.  Also, this meant that it is possible to boot Old3DS firmware on New3DS (see &amp;quot;CFG9_SYSPROT9 bit1 not set by Kernel9&amp;quot;).&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| October 2014&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| sighax: Boot9 improper validation of FIRM partition RSA signatures&lt;br /&gt;
| The [[Flash_Filesystem|FIRM partitions]] are signed with RSA-2048 using SHA-256 and PKCS #1 v1.5 padding.  Boot9, however, improperly validates the padding in three ways:&lt;br /&gt;
# Boot9 permits block type 02, meant for encrypted messages, to be used for signatures.  Only 01, for signatures, should have been permitted.  As a result, when using block type 02, a signature block is not required to have a long string of FF bytes as padding, but rather any nonzero random values suffice.&lt;br /&gt;
# Boot9 does not require that the length of the padding fill out the signature block completely.  As a result, there is considerable freedom in the layout of a signature.&lt;br /&gt;
# Boot9 fails to do bounds checking in its parsing of the DER-encoded hash algorithm type and hash value; the length values given in DER are permitted to point outside the signature block.&lt;br /&gt;
Flaw 3 allows the DER encoding to be such that boot9 believes that the signature&#039;s hash value is outside the range of the block itself, somewhere on the stack.  This can be pointed at the correct hash value it computes.  Boot9 then memcmp&#039;s the calculated hash against itself, and thinks that the hash is valid.&lt;br /&gt;
&lt;br /&gt;
As a result of the above, we estimate that one in 2&amp;lt;sup&amp;gt;43&amp;lt;/sup&amp;gt; (~8.8 trillion) random fake signatures will be considered by Boot9 to be valid.  This is well within the range of brute force, particularly with an optimized GPU implementation.  An Nvidia GTX 1080 Ti would take about one week to find a match.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| July 2015&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| Boot9 FIRM loading doesn&#039;t blacklist memory-mapped I/O&lt;br /&gt;
| [[Bootloader|Boot9]]&#039;s FIRM loading blacklists Boot9 data regions, but forgets to do other important regions, including Memory-mapped I/O. Combined with sighax, a malicious FIRM can be used to overwrite:&lt;br /&gt;
a) boot9 data-abort handler, coupled with a 4th section that tries to NDMA copy to NULL, causing a data abort&lt;br /&gt;
&lt;br /&gt;
b) boot9 IRQ handler (this has the disadvantage that you must restore the original handler, then call it manually when your payload runs)&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| 2015(?)&lt;br /&gt;
| [[User:Derrek|derrek]] (2015?), [[User:Normmatt|Normmatt]] and [[User:SciresM|SciresM]] independently (January 2017).&lt;br /&gt;
|-&lt;br /&gt;
| &amp;quot;superhax&amp;quot;: Boot9 FIRM loading blacklist check is flawed&lt;br /&gt;
| Boot9 only makes sure the &#039;&#039;&#039;start&#039;&#039;&#039; and &#039;&#039;&#039;end&#039;&#039;&#039; address of each section is not covered by a blacklisted region. Thus, it is possible to overwrite blacklisted regions (e.g. ARM9 Exception Vectors) by choosing a FIRM section range that encloses an entire blacklisted region. The vulnerable code looks like this: if(blRegions[i].start &amp;lt;= sectionStart &amp;amp;&amp;amp; blRegions[i].end &amp;gt; sectionStart &amp;lt;nowiki&amp;gt;||&amp;lt;/nowiki&amp;gt; blRegions[i].start &amp;lt;= sectionEnd &amp;amp;&amp;amp; blRegions[i].end &amp;gt; sectionEnd) return false; // failure&lt;br /&gt;
The boot9 vector table (0x08000000) contains 6 entries, each 8-bytes wide (0x30 bytes); Only 0x08000000 through 0x08000040 are blacklisted, and boot9 doesn&#039;t use the region after the vector table (this is convenient because we can put any payload we want after it and not worry about overwriting chunks of boot9 code)&lt;br /&gt;
&lt;br /&gt;
To exploit this, craft a FIRM section payload that&#039;s loaded a few bytes before 0x08000000, add padding to get to 0x08000000 and overwrite the vector table; You could overwrite the data-abort vector and craft a 4th FIRM section that causes a data-abort OR you can just overwrite the IRQ function pointer at 0x08000004 (make sure your payload replaces the original boot9 function pointer); you can point the rest of the vectors to infinite loops since they shouldn&#039;t be triggered.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| August 2015&lt;br /&gt;
| [[User:Plutoo|plutoo]], [[User:Yellows8|yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ARM9 software ==&lt;br /&gt;
&lt;br /&gt;
=== arm9loader ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Public disclosure timeframe&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| Generating the keysector console-unique keys with ITCM+Boot9&lt;br /&gt;
| [[Bootloader|Boot9]] decrypts the 0x100-byte [[OTP_Registers|OTP]] using AES-CBC with keydata stored in Boot9. If hash verification is successful, the plaintext of the first 0x90-bytes are copied into [[Memory_layout|ITCM]]. This is the &#039;&#039;exact&#039;&#039; &#039;&#039;same&#039;&#039; region hashed by arm9loader when generating the console-unique keys for decrypting the keysector, except arm9loader uses the raw encrypted OTP.&lt;br /&gt;
&lt;br /&gt;
Therefore, with the OTP keydata+IV from Boot9 you can: encrypt the 0x90-bytes from ITCM, then hash the output to get the console-unique keys for the system&#039;s keysector. This can even be done for Old3DS which doesn&#039;t have the arm9loader keysector officially.&lt;br /&gt;
&lt;br /&gt;
It&#039;s unknown why arm9loader only used the first 0x90-bytes of OTP. Using more data from OTP would&#039;ve prevented this. Fixing this would require doing exactly that, but that would also mean updating the NAND keysector(which is dangerous).&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| &lt;br /&gt;
| 2015&lt;br /&gt;
| January 6, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Rearrangable keys in the NAND keystore&lt;br /&gt;
| Due to the keystore being encrypted with AES-ECB, one can rearrange blocks and still have the NAND keystore decrypt in a deterministic way. &lt;br /&gt;
&lt;br /&gt;
Using 10.0 FIRM it is possible to rearrange keys such that ARM9 memory is executed. As such using existing ARM9 execution 10.0 FIRM can be written to NAND and a payload written to memory, with the payload to be executed post-K9L using an MCU reboot.&lt;br /&gt;
| arm9loaderhax given existing ARM9 code execution&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| Early 2016&lt;br /&gt;
| 27 September 2016&lt;br /&gt;
| Myria, [[User:Dark samus|dark_samus]]; mathieulh (independently); [[User:Plutooo|plutoo]] (independently) + others&lt;br /&gt;
|-&lt;br /&gt;
| Uncleared OTP hash keydata in console-unique 0x11 key-generation&lt;br /&gt;
| Kernel9Loader does not clear the [[SHA_Registers#SHA_HASH|SHA_HASH register]] after use. As a result, the data stored here as K9L hands over to Kernel9 is the hash of [[OTP_Registers|OTP data]] used to seed the [[FIRM#New_3DS_FIRM|console-unique NAND keystore decryption key]] set on keyslot 0x11.&lt;br /&gt;
&lt;br /&gt;
Retrieving this keydata and the [[Flash_Filesystem#0x12C00|NAND keystore]] of the same device allows calculating the decrypted New3DS NAND keystore (non-unique, common to all New3DS units), which contains AES normal keys, also set on keyslot 0x11, which are then used to derive all current [[AES_Registers#Keyslots|New3DS-only AES keyXs]] including the newer batch introduced in [[9.6.0-24#arm9loader|9.6.0-X]]. From there, it is trivial to perform the same key derivation in order to initialize those keys on any system version, and even on Old3DS.&lt;br /&gt;
&lt;br /&gt;
This can be performed by exploiting the &amp;quot;arm9loaderhax&amp;quot; vulnerability to obtain post-K9L code execution after an MCU reboot (the bootrom section-loading fail is not relevant here, this attack was performed without OTP data by brute-forcing keys), and using this to dump the SHA_HASH register. This attack works on any FIRM version shipping a vulnerable version of K9L, whereas OTP dumping required a boot of &amp;lt;[[3.0.0-6|3.0.0-X]].&lt;br /&gt;
&lt;br /&gt;
This attack results in obtaining the entire (0x200-bytes) NAND keystore - it was confirmed at a later date that this keystore is encrypted with the same key (by comparing the decrypted data from multiple units), and therefore using another key in this store will not remedy the issue as all keys are known (i.e. later, unused keys decrypt to the same 0x200-bytes constant with the same OTP hash). Later keys could have been encrypted differently but this is not the case. As a result of this, it is not possible for Nintendo to use K9L again in its current format for its intended purpose, though this was not news from the moment people dumped a New3DS OTP.&lt;br /&gt;
| Derivation of all New3DS keys generated via the NAND keystore (0x1B &amp;quot;Secure4&amp;quot; etc.)&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| ~April 2015, implemented in May 2015&lt;br /&gt;
| 13 January 2016&lt;br /&gt;
| [[User:WulfyStylez|WulfyStylez]], [[User:Dazzozo|Dazzozo]], [[User:Shinyquagsire23|shinyquagsire23]] (complimentary + implemented), [[User:Plutooo|plutoo]], Normmatt (discovered independently)&lt;br /&gt;
|-&lt;br /&gt;
| enhanced-arm9loaderhax&lt;br /&gt;
| See the 32c3 3ds talk.&lt;br /&gt;
Since this is a combination of a trick with the arm9-bootrom + arm9loaderhax, and since you have to manually write FIRM to the firm0/firm1 NAND partitions, this can&#039;t be completely fixed. Any system with existing ARM9 code execution and an OTP/OTP hash dump can exploit this. Additionally, by using the FIRM partition known-plaintext bug and bruteforcing the second entry in the keystore, this can currently be exploited on all New3DS systems without any other prerequisite hacks.&lt;br /&gt;
| arm9loaderhax which automatically occurs at hard-boot.&lt;br /&gt;
| See arm9loaderhax / description.&lt;br /&gt;
| See arm9loaderhax / description.&lt;br /&gt;
| Theorized around mid July, 2015. Later implemented+tested by [[User:Plutooo|plutoo]] and [[User:Derrek|derrek]].&lt;br /&gt;
| 32c3 3ds talk (December 27, 2015)&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| arm9loaderhax: Missing verification block for the 9.6 keys&lt;br /&gt;
| Starting with [[9.6.0-24|9.6.0-X]] a new set of NAND-based keys were introduced. However, no verification block was added to verify that the new key read from NAND is correct. This was technically an issue from [[9.5.0-22|9.5.0-X]] with the original sector+0 keydata, however the below is only possible with [[9.6.0-24|9.6.0-X]] since keyslots 0x15 and 0x16 are generated from different 0x11 keyXs.&lt;br /&gt;
&lt;br /&gt;
Writing an incorrect key to NAND will cause arm9loader to decrypt the ARM9 kernel as garbage and then jump to it.&lt;br /&gt;
&lt;br /&gt;
This allows an hardware-based attack where you can boot into an older exploited firmware, fill all memory with NOP sleds/jump-instructions, and then reboot into executing garbage. By automating this process with various input keydata, eventually you&#039;ll find some garbage that jumps to your code.&lt;br /&gt;
&lt;br /&gt;
This gives very early ARM9 code execution (pre-ARM9 kernel). As such, it is possible to dump RSA keyslots with this and calculate the 6.x [[Savegames#6.0.0-11_Savegame_keyY|save]], and 7.x [[NCCH]] keys. This cannot be used to recover keys initialized by arm9loader itself. This is due to it wiping the area used for its stack during NAND sector decryption and keyslot init. &lt;br /&gt;
&lt;br /&gt;
Due to FIRMs on both Old and New 3DS using the same RSA data, this can be exploited on Old3DS as well, but only if one already has the actual plaintext normalkey from New3DS NAND sector 0x96 offset-0 and has dumped the OTP area of the Old3DS.&lt;br /&gt;
| Recovery of 6.x [[Savegames#6.0.0-11_Savegame_keyY|save key]]/7.x [[NCCH]] key, access to uncleared OTP hash keydata&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| arm9loader runs on Old3DS&lt;br /&gt;
| Despite being written only for New3DS, all of arm9loader runs fine on Old3DS.  It&#039;s not until booting Kernel9 that a New3DS FIRM partition would crash on an Old3DS.  As a result, if a bug exists in arm9loader to get control, it can be exploited on Old3DS by writing New3DS FIRM to the FIRM partitions.  Thus, arm9loaderhax works on both Old3DS and New3DS.&lt;br /&gt;
| arm9loader bugs also compromise Old3DS&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| Sometime in 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]] presumably&lt;br /&gt;
|-&lt;br /&gt;
| Uncleared New3DS keyslot 0x11&lt;br /&gt;
| Originally the New3DS [[FIRM]] arm9bin loader only cleared keyslot 0x11 when it gets executed at firmlaunch. This was fixed with [[9.5.0-22|9.5.0-X]] by completely clearing keyslot 0x11 immediately after the loader finishes using keyslot 0x11.&lt;br /&gt;
This means that any ARM9 code that can execute before the loader clears the keyslot at firmlaunch(including firmlaunch-hax) can get access to the uncleared keyslot 0x11, which then allows one to generate all &amp;lt;=v9.5 New3DS keyXs which are generated by keyslot 0x11.&lt;br /&gt;
&lt;br /&gt;
Therefore, to completely fix this the loader would have to generate more keys using different keyslot 0x11 keydata. This was done with [[9.6.0-24|9.6.0-X]].&lt;br /&gt;
| New3DS keyXs generation&lt;br /&gt;
| Mostly fixed with [[9.5.0-22|9.5.0-X]], completely fixed with new keys with [[9.6.0-24|9.6.0-X]].&lt;br /&gt;
| &lt;br /&gt;
| February 3, 2015 (one day after [[9.5.0-22|9.5.0-X]] release)&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Process9 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Public disclosure timeframe&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| Leak of normal-key matching a key-scrambler key&lt;br /&gt;
| New 3DS firmware versions [[8.1.0-0 New3DS|8.1.0]] through [[9.2.0-20|9.2.0]] set the encryption key for [[Amiibo]] data using a hardcoded normal-key in Process9.  In firmware [[9.3.0-21|9.3.0]], Nintendo &amp;quot;fixed&amp;quot; this by using the key scrambler instead, by calculating the keyY value for keyslot 0x39 that results in the same normal-key, then hardcoding that keyY into Process9.&lt;br /&gt;
&lt;br /&gt;
Nintendo&#039;s fix is actually the problem: Nintendo revealed the normal-key matching an unknown keyX and a known keyY.  Combined with the key scrambler using an insecure scrambling algorithm (see &amp;quot;Hardware&amp;quot; above), the key scrambler function could be deduced.&lt;br /&gt;
| Deducing the keyX for keyslot 0x39 and the key scrambler algorithm&lt;br /&gt;
| New 3DS [[9.3.0-21|9.3.0-X]], sort of&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| Sometime in 2015 after the hardware key-generator was broken.&lt;br /&gt;
| 32c3 3ds talk (December 27, 2015)&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Leak of normal-key matching a key-generator key&lt;br /&gt;
| During the 3DS&#039; development (June/July 2010) Nintendo added support installing encrypted content ([[CIA]]). Common-key index1 was intended to be a [[AES|hardware generated key]]. However while they added code to generate the key in hardware, they forgot to remove the normal-key for index1 (used elsewhere, likely old debug code). Nintendo later removed the normal key sometime before the first non-prototype firmware release.&lt;br /&gt;
&lt;br /&gt;
Knowing the keyY and the normal-key for common-key index1, the devkit key-generator algorithm can be deduced (see &amp;quot;Hardware&amp;quot; above). Additionally the remaining devkit common-keys can be generated once the common-key keyX is recovered.&lt;br /&gt;
&lt;br /&gt;
Note that the devkit key-generator was discovered to be the same as the retail key-generator.&lt;br /&gt;
| Deducing the keyX for keyslot 0x3D and hardware key-generator algorithm. Generate remaining devkit common-keys.&lt;br /&gt;
| pre-[[1.0.0-0|1.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| Shortly after the key-generator was revealed to be flawed at the 32c3 3ds talk&lt;br /&gt;
| January 20, 2016&lt;br /&gt;
| [[User:Jakcron|jakcron]]&lt;br /&gt;
|-&lt;br /&gt;
| Factory firmware is vulnerable to sighax&lt;br /&gt;
| During the 3DS&#039;s development, presumably boot9 was written (including the sighax vulnerability). This vulnerability is also present in factory firmware (and earlier, including 0.11). This was fixed in version 1.0.0-0.&lt;br /&gt;
| Deducing the mechanics of the sighax vulnerability in boot9 without having a dump of protected boot9. ARM9 code execution on factory/earlier firmware.&lt;br /&gt;
| [[1.0.0-0|1.0.0-X]]&lt;br /&gt;
| [[1.0.0-0|1.0.0-X]]&lt;br /&gt;
| May 9, 2017&lt;br /&gt;
| May 19, 2017&lt;br /&gt;
| [[User:SciresM|SciresM]], [[User:Myria|Myria]]&lt;br /&gt;
|-&lt;br /&gt;
| safecerthax &lt;br /&gt;
| O3DS &amp;amp; O2DS SAFE_FIRM is still vulnerable to the PXIAM:ImportCertificates flaw fixed in [[5.0.0-11]] and to SSLoth fixed in [[11.14.0-46]]. It makes it possible to spoof the official NUS update server and remotely trigger the vulnerability in SAFE_FIRM.&lt;br /&gt;
| Remote Arm9 code execution in O3DS/O2DS SAFE_FIRM&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2020&lt;br /&gt;
| December 18, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| twlhax: Corrupted SRL header leads to memory overwrite&lt;br /&gt;
| During TWL_FIRM boot, the ARM11 process TwlBg puts launcher.srl, the DSi bootloader, into FCRAM.  TWL_FIRM Process9 then parses the [http://dsibrew.org/wiki/NDS_Format SRL header] to place launcher.srl&#039;s code where DSi mode can execute it.&lt;br /&gt;
&lt;br /&gt;
DSi-mode memory is in FCRAM, but interleaved.  Each byte of DSi-mode memory also exists at some address in 3DS FCRAM space.&lt;br /&gt;
&lt;br /&gt;
Process9 does not validate the RSA signature on launcher.srl, unlike SRLs loaded from cartridge or NAND (DSiWare).  A compromised ARM11 can, in a manner similar to firmlaunchhax, send a launcher.srl with a modified SRL header.  By setting the SRL header&#039;s ARM7/ARM9 load addresses and sizes carefully, accounting for the different memory map and for DSi mode&#039;s interleaved memory, it is possible to overwrite part of Process9&#039;s stack and take control with a ROP chain.&lt;br /&gt;
&lt;br /&gt;
Fixed in 11.8.0-X by... (fill me in)&lt;br /&gt;
| ARM9 code execution (whilst still in 3DS mode)&lt;br /&gt;
| [[11.8.0-41|11.8.0-X]]&lt;br /&gt;
| [[11.8.0-41|11.8.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| August 11, 2018&lt;br /&gt;
| smea&lt;br /&gt;
|-&lt;br /&gt;
| agbhax&lt;br /&gt;
| This is the same issue as twlhax above. Legacy FIRMs share the same OS code (Arm9-side OS, Arm11 kernel), and therefore, the outdated AGB_FIRM can be tricked into executing the still vulnerable PrepareArm9ForTwl function.&lt;br /&gt;
| ARM9 code execution (whilst still in 3DS mode)&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| December 17, 2020&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| safefirmhax&lt;br /&gt;
| SAFE_MODE_FIRM is almost never updated(even when NATIVE_FIRM is updated for vuln fixes), this can be noticed by &#039;&#039;just&#039;&#039; checking 3dbrew/ninupdates title-listings.&lt;br /&gt;
&lt;br /&gt;
The fix for firmlaunch-hax was only applied to NATIVE_FIRM in [[9.5.0-22|9.5.0-X]], leaving SAFE_FIRM exploitable. With ARM11-kernel execution, one can trigger FIRM-launch in to SAFE_FIRM, do Kernel9 &amp;lt;=&amp;gt; Kernel11 sync, PXI sync and then repeat the original attack on SAFE_FIRM instead.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| 2012-2013?&lt;br /&gt;
| Wiki: January 2, 2017&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| safefirmhax 1.1&lt;br /&gt;
| Nintendo&#039;s original safefirmhax fix was flawed -- they added a global boolean that got set to true whenever a non-sysmodule title got launched (except for a hardcoded repair title id), and panic()&#039;d if that boolean was true to prevent launching safefirm after hax was active. However, because the boolean was initially false after firmlaunch -- With ARM11-kernel execution, one could FIRM-launch into NATIVE_FIRM, and then immediately FIRM-launch again into SAFE_FIRM early in NATIVE_FIRM boot before the boolean got set to true to repeat the safehax attack.&lt;br /&gt;
&lt;br /&gt;
This was fixed by adding additional CFG9_BOOTENV checks to firmlaunch code in 11.4.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[11.4.0-37|11.4.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| safefirmhax fix&lt;br /&gt;
| Wiki: April 10, 2017&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| ntrcardhax&lt;br /&gt;
| When reading the banner of a NTR title, Process9 relies on a hardware register to know when the banner was fully read.&lt;br /&gt;
However that register is shared between the ARM9 and the ARM11.&lt;br /&gt;
An attacker with k11 control can so make Process9 believe the banner continues forever and so trigger a buffer overflow.&lt;br /&gt;
With a custom banner for a NTR flashcart, this leads to code execution in Process9.&lt;br /&gt;
&lt;br /&gt;
This was fixed by adding bound checks on the read data.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[10.4.0-29|10.4.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| March 2015&lt;br /&gt;
| 32c3 3ds talk (December 27, 2015)&lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| Title downgrading via [[Application_Manager_Services|AM]]([[Application_Manager_Services_PXI|PXI]])&lt;br /&gt;
| When a title is *already* installed, Process9 will compare the installed title-version with the title-version being installed. When the one being installed is older, Process9 would return an error.&lt;br /&gt;
&lt;br /&gt;
However, this can be bypassed by just deleting the title first via the service command(s) for that: with the title removed from the [[Title_Database]], Process9 can&#039;t compare the input title-version with anything. Hence, titles can be downgraded this way.&lt;br /&gt;
&lt;br /&gt;
[[11.0.0-33|11.0.0-X]] fixed this for key system titles (MSET, Home Menu, spider, ErrDisp, SKATER, NATIVE_FIRM, and every retail system module), by checking the version of the title to install against a hard-coded list of (titleID, minimumVersionRequired) pairs.&lt;br /&gt;
| Bypassing title version check at installation, which then allows downgrading any title.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]], for key system titles.&lt;br /&gt;
| NATIVE_FIRM / AM-sysmodule [[11.0.0-33|11.0.0-X]]&lt;br /&gt;
| ?&lt;br /&gt;
| &lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| Anti-downgrade list did not include all system titles initially&lt;br /&gt;
| The anti-downgrade list did not include legacy FIRMs until [[11.8.0-41|11.8.0-X]]. Therefore, legacy FIRMs could still be downgraded.&lt;br /&gt;
| Downgrading legacy FIRMs; allowing to exploit bugs in older legacy FIRMs (of which at least one exists, see below).&lt;br /&gt;
| [[11.8.0-33|11.8.0]]&lt;br /&gt;
| [[11.8.0-33|11.8.0]]&lt;br /&gt;
| ?&lt;br /&gt;
| Wiki: August 5, 2018&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| TWL_FIRM cmd-9 unchecked offset&lt;br /&gt;
| In [[1.0.0-0|1.0.0-X]]&#039;s TWL_FIRM, cmds 8 and 9 were not stubbed (whereas in the corresponding NATIVE_FIRM, they were).&lt;br /&gt;
Command 8 does the Process9 initialisation for NTR carts if an NTR cart is inserted (NTR, not TWL, judged by chipid).&lt;br /&gt;
&lt;br /&gt;
Command 9 takes (u32 offset_read, u32 offset_write, u32 offset_read_end), and basically just copies (offset_read_end - offset_read) bytes starting at (offset_read) of [NTR cart header in arm9mem, NTR secure area in fcram, TWL secure area in fcram], to 0x18001000 + offset_write + offset_read.&lt;br /&gt;
&lt;br /&gt;
offset_write is not checked at all, thus this leads to ARM9 code execution as long as any NTR cart, including flashcarts that would normally be blocked by TWL_FIRM, is inserted.&lt;br /&gt;
&lt;br /&gt;
In [[2.0.0-2|2.0.0-X]] TWL_FIRM, those commands were stubbed out.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[2.0.0-2|2.0.0-X]]&lt;br /&gt;
| [[2.0.0-2|2.0.0-X]]&lt;br /&gt;
| January 2018&lt;br /&gt;
| Wiki: August 5, 2018&lt;br /&gt;
| [[User:Riley|Riley]]&lt;br /&gt;
|-&lt;br /&gt;
| FIRM launch doesn&#039;t check target FIRM version&lt;br /&gt;
| When executing a FIRM launch, Process9 doesn&#039;t validate that the target FIRM isn&#039;t an old version.  This allows booting an exploitable FIRM from a newer FIRM, if you can get the exploitable FIRM installed.  ([[11.0.0-33|11.0.0-X]] now prevents installing old versions of system titles, but this doesn&#039;t affect titles already installed.)&lt;br /&gt;
&lt;br /&gt;
This had a use after [[9.6.0-24|9.6.0-X]]: on a compromised 3DS running 9.2.0, you could install the 9.6.0 NATIVE_FIRM to FIRM0/FIRM1, but avoid putting it into the NATIVE_FIRM title.  This would boot the 9.2.0 system software but with the 9.6.0 Process9 and Kernel11.  With a user-mode exploit in a sufficiently-privileged application (e.g. mset), you could trigger a FIRM launch back to NATIVE_FIRM, which would load the 9.2.0 Process9 and Kernel11.&lt;br /&gt;
&lt;br /&gt;
9.6.0&#039;s keyslots 0x15 and 0x16 are unknown to 9.2.0, so 9.2.0 would not clear them.  You then could do firmlaunchhax against 9.2.0 to get ARM9 access with keyslots 0x15 and 0x16 set to their proper 9.6.0 values, allowing decrypting 9.6.0&#039;s encrypted titles.  Once the New3DS keystore was dumped, this became moot.&lt;br /&gt;
| Decrypting 9.6.0 NCCH files without dumping New3DS keystore&lt;br /&gt;
| None (but now moot)&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| August 12, 2018&lt;br /&gt;
| [[User:Yellows8|Yellows8]], [[User:Myria|Myria]]&lt;br /&gt;
|-&lt;br /&gt;
| FAT FS code null-deref&lt;br /&gt;
| When FSFile:Read is used with a file which is corrupted on a FAT filesystem(in particular SD), Process9 can crash. This particular crash is caused by a function returning NULL instead of an actual ptr due to an error. The caller of that function doesn&#039;t check for NULL which then triggers a read based at NULL.&lt;br /&gt;
&lt;br /&gt;
Sample &amp;quot;fsck.vfat -n -v -V &amp;lt;fat image backup&amp;gt;&amp;quot; output for the above crash:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;...&lt;br /&gt;
Starting check/repair pass.&lt;br /&gt;
&amp;lt;FilePath0&amp;gt; and&lt;br /&gt;
&amp;lt;FilePath1&amp;gt;&lt;br /&gt;
 share clusters.&lt;br /&gt;
 Truncating second to 3375104 bytes.&lt;br /&gt;
&amp;lt;FilePath1&amp;gt;&lt;br /&gt;
 File size is 2787392 bytes, cluster chain length is 16384 bytes.&lt;br /&gt;
 Truncating file to 16384 bytes.&lt;br /&gt;
Checking for unused clusters.&lt;br /&gt;
Reclaimed 1 unused cluster (16384 bytes).&lt;br /&gt;
Checking free cluster summary.&lt;br /&gt;
Free cluster summary wrong (1404490 vs. really 1404491)&lt;br /&gt;
 Auto-correcting.&lt;br /&gt;
Starting verification pass.&lt;br /&gt;
Checking for unused clusters.&lt;br /&gt;
Leaving filesystem unchanged.&amp;lt;/pre&amp;gt;&lt;br /&gt;
| Useless null-based-read&lt;br /&gt;
| None&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| July 8-9, 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[FS:EnumerateExtSaveData]] crashes process9 when trying to parse a file as an extdata directory in Data Management (MSET9)&lt;br /&gt;
| In the implementation for FSPXI:EnumerateExtSaveData (called by [[System_Settings|MSET]] to parse 3DS extdata IDs for Data Management), the return value of the P9 internal function call to open a directory (when enumerating contents of the extdata directory) was not checked. Therefore, if the call fails, an uninitialised pointer on stack will be used for a vtable call.&lt;br /&gt;
&lt;br /&gt;
As such, a file that starts with 8 hex digits can crash process9 if placed directly inside the extdata directory. It can crash in various ways based on subtle differences in the way the user triggers the crash event.&lt;br /&gt;
&lt;br /&gt;
While mostly leading to null derefs, in one specific context, process9 jumps directly to an ID1 string being held in ARM9 memory. Surprisingly, the 3DS doesn&#039;t discern what characters are used for the ID1 directory name on the SD, only requiring exactly 32 chars. This allows the attacker to insert arm instructions into the unicode ID1 dirname and take control of the ARM9, and thus, full control of the 3DS.&lt;br /&gt;
| ARM9 code execution (primary)&lt;br /&gt;
| None&lt;br /&gt;
| [[11.17.0-50|11.17.0-X]]&lt;br /&gt;
| April 2022&lt;br /&gt;
| August 7, 2023&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| RSA signature padding checks&lt;br /&gt;
| The TWL_FIRM RSA sig padding check code used for all TWL RSA sig-checks has issues, see [[FIRM|here]].&lt;br /&gt;
The main 3DS RSA padding check code(non-certificate, including NATIVE_FIRM) uses the function used with the above to extract more padding + the actual hash from the additional padding. This isn&#039;t really a problem here because there&#039;s proper padding check code which is executed prior to this.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.5.0-22|9.5.0-X]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[AMPXI:ValidateDSiWareSectionMAC]] [[AES_Registers|AES]] keyslot reuse&lt;br /&gt;
| When the input DSiWare section index is higher than &amp;lt;max number of DSiWare sections supported by this FIRM&amp;gt;, Process9 uses keyid 0x40 for calculating the AESMAC, which translates to keyslot 0x40. The result is that the keyslot is left at whatever was already selected before, since the AES selectkeyslot code will immediately  return when keyslot is &amp;gt;=0x40. However, actually exploiting this is difficult: the calculated AESMAC is never returned, this command just compares the calculated AESMAC with the input AESMAC(result-code depends on whether the AESMACs match). It&#039;s unknown whether a timing attack would work with this.&lt;br /&gt;
This is basically a different form of the pxips9 keyslot vuln, except with AESMAC etc.&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| March 15, 2015&lt;br /&gt;
| December 29, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| pxips9 [[AES_Registers|AES]] keyslot reuse&lt;br /&gt;
| This requires access to the [[Process_Services|ps:ps]]/pxi:ps9 services. One way to get access to this would be snshax on system-version &amp;lt;=10.1.0-X(see 32c3 3ds talk).&lt;br /&gt;
When an invalid key-type value is passed to any of the PS commands, Process9 will try to select keyslot 0x40. That aesengine_setkeyslot() code will then immediately return due to the invalid keyslot value. Since that function doesn&#039;t return any errors, Process9 will just continue to do crypto with whatever AES keyslot was selected before the PS command was sent.&lt;br /&gt;
| Reusing the previously used keyslot, for crypto with PS.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| Roughly the same time(same day?) as firmlaunch-hax.&lt;br /&gt;
| December 29, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| firmlaunch-hax: FIRM header ToCToU&lt;br /&gt;
| This can&#039;t be exploited from ARM11 userland.&lt;br /&gt;
During [[FIRM]] launch, the only FIRM header the ARM9 uses at all is stored in FCRAM, this is 0x200-bytes(the actual used FIRM RSA signature is read to the Process9 stack however). The ARM9 doesn&#039;t expect &amp;quot;anything&amp;quot; besides the ARM9 to access this data.&lt;br /&gt;
With [[9.5.0-22]] the address of this FIRM header was changed from a FCRAM address, to ARM9-only address 0x01fffc00.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[9.5.0-22]]&lt;br /&gt;
| &lt;br /&gt;
| 2012, 3 days after [[User:Yellows8|Yellows8]] started Process9 code RE.&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Uninitialized data output for (PXI) command replies&lt;br /&gt;
| PXI commands for various services(including some [[Filesystem_services_PXI|here]] and many others) can write uninitialized data (like from ARM registers) to the command reply. This happens with stubbed commands, but this can also occur with certain commands when returning an error.&lt;br /&gt;
Certain ARM11 service commands have this same issue as well.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.3.0-21|9.3.0-X]]&lt;br /&gt;
| ?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Filesystem_services_PXI|FSPXI]] OpenArchive SD permissions&lt;br /&gt;
| Process9 does not use the exheader ARM9 access-mount permission flag for SD at all.&lt;br /&gt;
This would mean ARM11-kernelmode code / fs-module itself could directly use FSPXI to access SD card without ARM9 checking for SD access, but this is rather useless since a process is usually running with SD access(Home Menu for example) anyway.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.3.0-21|9.3.0-X]]&lt;br /&gt;
| 2012&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[AMPXI:ExportDSiWare]] export path&lt;br /&gt;
| Process9 allocates memory on Process9 heap for the export path then verifies that the actual allocated size matches the input size. Then Process9 copies the input path from FCRAM to this buffer, and uses it with the Process9 FS openfile code, which use paths in the form of &amp;quot;&amp;lt;mountpoint&amp;gt;:/&amp;lt;path&amp;gt;&amp;quot;.&lt;br /&gt;
Process9 does not check the contents of this path at all before passing it to the FS code, besides writing a NUL-terminator to the end of the buffer.&lt;br /&gt;
| Exporting of DSiWare to arbitrary Process9 file-paths, such as &amp;quot;nand:/&amp;lt;path&amp;gt;&amp;quot; etc. This isn&#039;t really useful since the data which gets written can&#039;t be controlled.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.5.0-22]]&lt;br /&gt;
| April 2013&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[DSiWare_Exports]] [[CTCert]] verification&lt;br /&gt;
| Just like DSi originally did, 3DS verifies the APCert for DSiWare on SD with the CTCert also in the DSiWare .bin. On DSi this was fixed with with system-version 1.4.2 by verifying with the actual console-unique cert instead(stored in NAND), while on 3DS it&#039;s still not fixed.&lt;br /&gt;
On 3DS this is used in conjunction with seedminer to be able to decrypt &amp;amp; modify DSiWare TAD containers and inject them with exploitable DSiWare titles such as sudoku (sudokuhax) and Flipnote JPN (ugopwn)&lt;br /&gt;
| When the movable.sed keyY for the target 3DS is known and the target 3DS CTCert private-key is unknown, importing of modified DSiWare SD .bin files.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.10.0-X&lt;br /&gt;
| April 2013&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| seedminer: movable.sed keyY vulnerable to brute-force&lt;br /&gt;
| Half of the movable.sed keyY&#039;s 128 bits are leaked through the [[Nandrw/sys/LocalFriendCodeSeed_B|LFCS]], which is available in userland and below. The LFCS itself also leaks almost half of the remaining bits by following the ratio: u32 keyY[3]=1/5(LFCS). The remaining keyY[3] uncertainty of about ±2000 can be greatly reduced by plotting expected error margins with several keyYs. This results in a final uncertainty of about 2^40, easily within practical brute force range of an average modern PC.&lt;br /&gt;
| Knowing the keyY of a given 3ds allows for modification of DSiWare export contents, and chained with several other public vulns, ultimately arm9 execution.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.8.0-X&lt;br /&gt;
| December 2017&lt;br /&gt;
| January 2018&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| Improper validation of DSiWare title SRLs&lt;br /&gt;
| The 3DS does not verify if the actual SRL embedded in the title&#039;s directory matches the titleID in the TMD before launching it or importing it from an sd DSiWare export. &lt;br /&gt;
| This allows embedding older, exploitable DSiWare titles in completely different, unexploitable DSiWare titles. Since DSiWare has raw NAND RW, this can result in arm9 control through FIRM known-plaintext and sighax attacks.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.10.0-X&lt;br /&gt;
| 2015?&lt;br /&gt;
| December 2016&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| DSiWare import/export functions allow TWL system titles as arguments&lt;br /&gt;
| AM ImportTwlBackup/ExportTwlBackup unnecessarily allow TWL system titles such as DS Download Play to import/export from userland and System Settings -&amp;gt; Data Management (only am:sys is needed for userland). This is difficult to abuse for dsihax injection because no TWL system title has a save file, and any import with a save included will result in FS err C8804464. However, there is at least one dsihax primary that can load a payload from a non-NAND source, and not error if it can&#039;t access its public.sav (JPN Flipnote Studio v0).&lt;br /&gt;
| When combined with other public vulns, arm9 code execution.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.10.0-X&lt;br /&gt;
| May 2018&lt;br /&gt;
| Sept 2018&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| [[Gamecard_Services_PXI]] unchecked REG_CTRCARDCNT transfer-size&lt;br /&gt;
| The u8 REG_CTRCARDCNT transfer-size parameter for the [[Gamecard_Services_PXI]] read/write CTRCARD commands is used as an index for an array of u16 values. Before [[5.0.0-11|5.0.0-X]] this u8 value wasn&#039;t checked, thus out-of-bounds reads could be triggered(which is rather useless in this case).&lt;br /&gt;
| Out-of-bounds read for a value which gets written to a register.&lt;br /&gt;
| [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| 2013?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[PXI_Registers|PXI]] cmdbuf buffer overrun&lt;br /&gt;
| The Process9 code responsible [[PXI_Registers|PXI]] communications didn&#039;t verify the size of the incoming command before writing it to a C++ member variable. &lt;br /&gt;
| Probably ARM9 code execution&lt;br /&gt;
| [[5.0.0-11|5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| March 2015, original timeframe if any unknown&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]]/[[User:Yellows8|Yellows8]]/maybe others(?)&lt;br /&gt;
|-&lt;br /&gt;
| [[Application_Manager_Services_PXI|PXIAM]]:ImportCertificates (See also [[Application_Manager_Services|this]])&lt;br /&gt;
| When handling this command, Process9 allocates a 0x2800-byte heap buffer, then copies the 4 FCRAM input buffers to this heap buffer without checking the sizes at all(only the buffers with non-zero sizes are copied). Starting with [[5.0.0-11|5.0.0-X]], the total combined size of the input data must be &amp;lt;=0x2800.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| May 2013&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Process_Services_PXI|PS RSA]] commands buffer overflows&lt;br /&gt;
| pxips9 cmd1(not accessible via ps:ps) and VerifyRsaSha256: unchecked copy to a buffer in Process9&#039;s .bss, from the input FCRAM buffer. The buffer is located before the pxi cmdhandler threads&#039; stacks. SignRsaSha256 also has a buf overflow, but this isn&#039;t exploitable.&lt;br /&gt;
The buffer for this is the buffer for the signature data. With v5.0, the signature buffer was moved to stack, with a check for the signature data size. When the signature data size is too large, Process9 uses [[SVC|svcBreak]].&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[PXI_Registers|PXI]] pxi_id bad check&lt;br /&gt;
| The Process9 code responsible for [[PXI_Registers|PXI]] communications read pxi_id as a signed char. There were two flaws:&lt;br /&gt;
* They used it as index to a lookup-table without checking the value at all.&lt;br /&gt;
* Another function verified that pxi_id &amp;lt; 7, allowing negative values to pass the check. This would also cause an out-of-range table-lookup.&lt;br /&gt;
| Maybe ARM9 code execution&lt;br /&gt;
| [[3.0.0-5|3.0.0-5]]&lt;br /&gt;
|&lt;br /&gt;
| March 2015, originally 2012 for the first issue at least&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]], maybe others(?)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Kernel9 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[CONFIG Registers#CFG9_SYSPROT9|CFG9_SYSPROT9]] bit1 not set by Kernel9&lt;br /&gt;
| Old versions of Kernel9 never set bit1 of [[CONFIG Registers#CFG9_SYSPROT9|CFG9_SYSPROT9]]. This leaves the [[OTP Registers|0x10012000]]-region unprotected (this region should be locked early during boot!). Since it&#039;s never locked, you can dump it once you get ARM9 code execution.&lt;br /&gt;
&lt;br /&gt;
From [[3.0.0-5|3.0.0-X]] this was fixed by setting the bit in Kernel9 after poking some registers in that region. On New3DS arm9loader sets this bit instead of Kernel9, which is exploitable through a hardware + software vulnerability (see arm9loaderhax / description).&lt;br /&gt;
&lt;br /&gt;
This flaw resurged when it gained a new practical use: retrieving the OTP data for a New3DS console in order to decrypt the key data used in arm9loader (see enhanced-arm9loaderhax / description). This was performed by downgrading to a vulnerable system version. By accounting for differences in CTR-NAND crypto (0x05 -&amp;gt; 0x04, see partition encryption types [[Flash_Filesystem#NAND_structure|here]]) and using an Old3DS [[NCSD#NCSD_header|NCSD Header]], it is possible to boot a New3DS using Old3DS firmware 1.0-2.x to retrieve the required OTP data using this flaw.&lt;br /&gt;
| Dumping the [[OTP Registers|OTP]] area.&lt;br /&gt;
Decrypting New3DS sector 0x96 keyblock.&lt;br /&gt;
| [[3.0.0-5|3.0.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| February 2015&lt;br /&gt;
| [[User:Plutooo|plutoo]], Normmatt independently&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ARM11 software ==&lt;br /&gt;
=== Kernel11 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcUnbindInterrupt]] double free when irqId = 15&lt;br /&gt;
| svcBindInterrupt and svcUnbindInterrupt give special treatment to irqId 15 (FIQ helper): the access control list is bypassed and the provided KInterruptEvent (event or semaphore, via handle) is stored inside a singleton static object after having its refcount increased by 1.&lt;br /&gt;
&lt;br /&gt;
svcUnbindInterrupt assumes that the user-provided handle is what is stored in the singleton and will decref the user-provided KInterruptEvent twice, causing a use-after-free if the attacker didn&#039;t actually provide an handle to the same event or semaphore.&lt;br /&gt;
&lt;br /&gt;
This was &amp;quot;fixed&amp;quot; on [[11.14.0-46|11.14.0-X]] by preventing irqId 15 to be bound on retail units altogether (in both functions).&lt;br /&gt;
| Arm11 kernel code execution&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]] (only on retail units)&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2019&lt;br /&gt;
| [[User:TuxSH|TuxSH]], maybe others&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcKernelSetState]] op=3 could map the NULL page&lt;br /&gt;
| svcKernelSetState op=3 param1=1 maps the firmlaunch parameters page to the user-specified VA.&lt;br /&gt;
&lt;br /&gt;
It had previously no check, allowing the attacker to map data at VA 0.&lt;br /&gt;
&lt;br /&gt;
Starting from [[11.14.0-46|11.14.0-X]], the VA must be in the standard 0x10000000-0x14000000 address range.&lt;br /&gt;
| Mapping the NULL page (as RW) to leverage other kernel vulnerabilities&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2019&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcMapProcessMemory]] can map the NULL page&lt;br /&gt;
| svcMapProcessMemory&#039;s destination VA is unchecked.&lt;br /&gt;
&lt;br /&gt;
By passing a big enough &amp;quot;size&amp;quot; parameter, an attacker can map chunks of data at VA 0 in the destination (caller) process.&lt;br /&gt;
| Mapping the NULL page (as RW) to leverage other kernel vulnerabilities&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2020&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| Resource limit use-after-free&lt;br /&gt;
| When assigning a KResourceLimit to a KProcess, the reslimit&#039;s refcounter doesn&#039;t get incremented. This essentially means all KResourceLimit get freed if pm gets somehow terminated.&lt;br /&gt;
&lt;br /&gt;
It turns out it is possible to ask pm (via ns:s or pm:app) to terminate itself along all other KIPs simply by passing TID 0004000100001000.&lt;br /&gt;
&lt;br /&gt;
Calling [[SVC|svcGetResourceLimit]] afterwards triggers a use-after-free. This is rather difficult to exploit, however: there is one slot left in the reslimit slabheap. An attacker either has to map the NULL page as R(W)X (svcControlProcessMemory vuln fixed on [[11.8.0-41|11.8.0-X]]), or use one of the map-null exploits above while having access to svcCreateResourceLimit (with the only one that is easy enough to use in that context having been fixed on [[11.14.0-46|11.14.0-X]], anyway).&lt;br /&gt;
| Arm11 kernel code execution&lt;br /&gt;
| None (although near impossible to exploit on [[11.14.0-46|11.14.0-X]])&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2020&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcSetProcessIdealProcessor]] reference count overflow and therefore use-after-free.&lt;br /&gt;
| The SVC receive two arguments: handle and idealprocessor. The handle is used to get the KProcess object and the KProcess-&amp;gt;refCnt gets incremented,later the function check if the KProcess-&amp;gt;mem_type != BASE and if yes, it checks for idealprocessor == 2 or idealprocessor != 3. The problem here is that if you pass the idealprocessor = 3 it won&#039;t meet any condition and return the error 0xD9001BEA without decrement the reference count. &lt;br /&gt;
It can be abused to overflow the KProcess reference count that will lead to an Use-after-free. &lt;br /&gt;
| Before [[11.2.0-35|11.2.0-X]]: reference count overflow and therefore use-after-free.&lt;br /&gt;
| &lt;br /&gt;
| [[11.6.0-39|11.6.0-X]]&lt;br /&gt;
| November 2, 2017&lt;br /&gt;
| [[User:st4rk|st4rk]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcGetThreadList]] process reference leak&lt;br /&gt;
| When given a valid process handle (including &amp;lt;code&amp;gt;0xFFFF8001&amp;lt;/code&amp;gt;), svcGetThreadList forgets to decrement the reference count of the underlying [[KProcess]] instance, after having finished using it.&lt;br /&gt;
| Before [[11.2.0-35|11.2.0-X]]: reference count overflow and therefore use-after-free, but this UAF was most likely not exploitable&lt;br /&gt;
| &lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| April 3, 2017&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| kernelhax via gspwn&lt;br /&gt;
| Originally the kernel didn&#039;t initialize [[CONFIG11_Registers#CFG11_GPUPROT|CFG11_GPUPROT]]. Since it&#039;s 0 at hard-boot, this allowed the GPU to access the entire FCRAM + AXIWRAM.&lt;br /&gt;
| Entire FCRAM+AXIWRAM R/W.&lt;br /&gt;
| [[3.0.0-5|3.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| February 7, 2017&lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]] partly&lt;br /&gt;
|-&lt;br /&gt;
| fasthax&lt;br /&gt;
| When a KTimer is created in pulse mode, the kernel calls a virtual function to reset the timer each time it pulses. The scheduler is locked for that core to avoid race conditions, but another core can call CloseHandle on the timer and free it, leading to a UAF vtable call.&lt;br /&gt;
| See description.&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| May 2016&lt;br /&gt;
| nedwill&lt;br /&gt;
|-&lt;br /&gt;
| ipctakeover&lt;br /&gt;
| When sending cmdreplies, it does not validate that the src_addr and src_size match the equivalent dst_addr and dst_size. With a modified addr/size specified in a cmdreply for an output buffer, the data-copy for the first/last pages could be used to overwrite data outside of the buffer specified by the original process.&lt;br /&gt;
&lt;br /&gt;
Used by ctr-httpwn as of v1.2, for &amp;quot;ipctakeover/bosshaxx&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This can be used to takeover processes where the process is using your service session. Like HTTPC -&amp;gt; BOSS, for bosshaxx above. NIM takeover can be done too(actual stack buffer overflow can trigger), etc.&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| November 26, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Using IPC input buffers as output buffers&lt;br /&gt;
| When sending cmdreplies, it does not validate that the cmdreply descriptor type matches the equivalent cmdreq descriptor type. This could be used by an exploited sysmodule to use what was intended as an input-buffer as an output-buffer, and also combine other IPC vuln(s) with this.&lt;br /&gt;
&lt;br /&gt;
Used by ctr-httpwn as of v1.2, for &amp;quot;ipctakeover/bosshaxx&amp;quot;.&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| November 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
|  [[SVC]] table too small&lt;br /&gt;
|  The table of function pointers for SVC&#039;s only contains entries up to 0x7D, but the biggest allowed SVC for the table is 0x7F. Thus, executing SVC7E or SVC7F would make the SVC-handler read after the buffer, and interpret some ARM instructions as function pointers.&lt;br /&gt;
&lt;br /&gt;
However, this would require patching the kernel .text or modifying SVC-access-control. Even if you could get these to execute, they would still jump to memory that isn&#039;t mapped as executable.&lt;br /&gt;
| &lt;br /&gt;
|  None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| 2012&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
|  [[SVC|svcBackdoor (0x7B)]]&lt;br /&gt;
|  This backdoor allows executing SVC-mode code at the user-specified code-address. This is used by Process9, using this on the ARM11 (with NATIVE_FIRM) required patching the kernel .text or modifying SVC-access-control.&lt;br /&gt;
| See description&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]] (deleted)&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| veryslowpidhax&lt;br /&gt;
| &#039;&#039;&#039;This is completely different from the kernelmode-code-execution vuln described in the below separate entry.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
When updating the kernel global PID counter under [[SVC|svcCreateProcess]] the kernel does not check for wraparound to 0x0(the PID for the very first process). This only matters because [[Services|SM-module]] allows processes with PID value less than &amp;lt;total ARM11 FIRM modules&amp;gt; to access &#039;&#039;all&#039;&#039; services, without checking exheader service-access-control; and because Kernel11 checks for the PID to be 1 (loader) to use the input mem-region value on ControlMemory. This alone does not affect access the [[SVC|SVCs]] access table at all.&lt;br /&gt;
&lt;br /&gt;
Inlined ldrex+strex code is used for updating the above counter. [[11.2.0-35|11.2.0-X]] had changes for similar code, but it was only for dedicated ldrex+strex functions(mainly for kernel objects) and hence this PID code was not affected.&lt;br /&gt;
&lt;br /&gt;
With launching+terminating a sysmodule repeatedly with this via ns:s, it would take weeks to finish(if not at least about a month?).&lt;br /&gt;
| Access to all [[Services_API|services]], ControlMemory on any given mem-region.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| 2012 maybe?&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
|  slowhax/waithax&lt;br /&gt;
|  svcWaitSynchronizationN does not decrement the references to valid handles in an array before returning an error when it encounters an invalid handle. This allows one to (slowly) overflow the reference count for a handle object to zero.&lt;br /&gt;
| ARM11 kernel-mode code execution&lt;br /&gt;
| [[11.2.0-35|11.2.0-X]]&lt;br /&gt;
| [[11.2.0-35|11.2.0-X]]&lt;br /&gt;
| 2016&lt;br /&gt;
| nedwill, [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Memory_layout#ARM11_Detailed_virtual_memory_map|0xEFF00000]] / 0xDFF00000 ARM11 kernel virtual-memory&lt;br /&gt;
| The ARM11 kernel-mode 0xEFF00000/0xDFF00000 virtual-memory(size 0x100000) is mapped to phys-mem 0x1FF00000(entire DSP-mem + entire AXIWRAM), with permissions RW-. This is used during ARM11 kernel startup for loading the FIRM-modules from the FIRM section located in DSP-mem, this never seems to be used after that, however. This is never unmapped either.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| memchunkhax2.1&lt;br /&gt;
| Nintendo&#039;s fix for memchunkhax2 in [[10.4.0-29|10.4.0-X]] did not fix the GPU case: one may cause the requisite ToCToU race using gspwn, bypassing the new validation.&lt;br /&gt;
derrek&#039;s original 32c3 presentation for memchunkhax2 commented that a GPU-based attack was possible, but would be difficult.  However, memchunkhax2.1 showed that it was possible to do fairly reliably.&lt;br /&gt;
| ARM11 kernel code execution&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]], via the new [[Memory_Management#MemoryBlockHeader|memchunkhdr]] MAC which prevents modifying memchunkhdr data with DMA.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| [[User:Derrek|derrek]], aliaspider&lt;br /&gt;
|-&lt;br /&gt;
| memchunkhax2&lt;br /&gt;
| When allocating a block of memory, the &amp;quot;next&amp;quot; pointer of the [[Memory_Management#MemoryBlockHeader|memchunkhdr]] is accessed without being checked after being mapped to userland.&lt;br /&gt;
This allows a race condition, where the process can change the next pointer just before it&#039;s accessed. By pointing the next pointer to a crafted memchunckhdr in the kernel SlabHeap, some of the SlabHeap is allocated to the calling process, allowing to change vtables of kernel objects. &lt;br /&gt;
| ARM11 kernel code execution&lt;br /&gt;
| [[10.4.0-29|10.4.0-X]] (partially, see memchunkhax2.1)&lt;br /&gt;
| [[10.4.0-29|10.4.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| heaphax&lt;br /&gt;
| Can change the size of free memchunk structures stored in FCRAM using DMA, which leads to the ability to allocate memory chunks over already-allocated memory. This can be used in the SYSTEM region to allocate RW memory over any part of the NS system module, which is enough to take it over.&lt;br /&gt;
| Code execution with access to all of NS&#039;s privileges. (including downgrading) Code execution within any applet.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]], via the new [[Memory_Management#MemoryBlockHeader|memchunkhdr]] MAC which prevents modifying memchunkhdr data with DMA.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]]&lt;br /&gt;
| April 2015 ?&lt;br /&gt;
| smea&lt;br /&gt;
|-&lt;br /&gt;
| snshax&lt;br /&gt;
| Can force creation of Safe NS process into gspwn-able memory, allowing for takeover.&lt;br /&gt;
| Code execution with access to all of NS&#039;s privileges. (including downgrading)&lt;br /&gt;
| [[10.1.0-27|10.1.0-X]]&lt;br /&gt;
| [[10.1.0-27|10.1.0-X]]&lt;br /&gt;
| April 2015 ?&lt;br /&gt;
| smea&lt;br /&gt;
|-&lt;br /&gt;
|  AffinityMask/processorid validation&lt;br /&gt;
|  With [[10.0.0-27|10.0.0-X]] the following functions were updated: svcGetThreadAffinityMask, svcGetProcessAffinityMask, svcSetProcessAffinityMask, and svcCreateThread. The code changes for all but svcCreateThread are identical.&lt;br /&gt;
The original code with the first 3 did the following: &lt;br /&gt;
* if(u32_processorcount &amp;gt; ~0x80000001)return 0xe0e01bfd;&lt;br /&gt;
* if(s32_processorcount &amp;gt; &amp;lt;total_cores&amp;gt;)return 0xd8e007fd;&lt;br /&gt;
The following code replaced the above:&lt;br /&gt;
* if(u32_processorcount &amp;gt;= &amp;lt;total_cores+1&amp;gt;)return 0xd8e007fd;&lt;br /&gt;
In theory the latter should catch everything that the former did, so it&#039;s unknown if this was really a security issue.&lt;br /&gt;
&lt;br /&gt;
The svcCreateThread changes with [[10.0.0-27|10.0.0-X]] definitely did fix a security issue.&lt;br /&gt;
* Original code: &amp;quot;if(s32_processorid &amp;gt; &amp;lt;total_cores&amp;gt;)return 0xd8e007fd;&amp;quot;&lt;br /&gt;
* New code: &amp;quot;if(s32_processorid &amp;gt;= &amp;lt;total_cores&amp;gt; || s32_processorid &amp;lt;= -4)return 0xd8e007fd;&amp;quot;&lt;br /&gt;
This fixed an off-by-one issue: if one would use processorid=total_cores, which isn&#039;t actually a valid value, svcCreateThread would accept that value on &amp;lt;[[10.0.0-27|10.0.0-X]]. This results in data being written out-of-bounds(baseaddr = arrayaddr + entrysize*processorid), which has the following result:&lt;br /&gt;
* Old3DS: Useless kernel-mode crash due to accessing unmapped memory.&lt;br /&gt;
* New3DS: uncontrolled data write into a kernel-mode L1 MMU-table. This isn&#039;t really useful: the data can&#039;t be controlled, and the data which gets overwritten is all-zero anyway(this isn&#039;t anywhere near MMU L1 entries for actually mapped memory).&lt;br /&gt;
The previous version also allowed large negative s32_processorid values(negative processorid values are special values not actual procids), but it appears using values like that won&#039;t actually do anything(meaning no crash) besides the thread not running / thread not running for a while(besides triggering a kernelpanic with certain s32_processorid value(s)).&lt;br /&gt;
| Nothing useful&lt;br /&gt;
|  [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| svcCreateThread issue: May 31, 2015. The rest: September 8, 2015, via v9.6-&amp;gt;v10.0 ARM11-kernel code-diff.&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| memchunkhax&lt;br /&gt;
| The kernel originally did not validate the data stored in the FCRAM kernel heap [[Memchunkhdr|memchunk-headers]] for free-memory at all. Exploiting this requires raw R/W access to these memchunk-headers, like physical-memory access with gspwn.&lt;br /&gt;
&lt;br /&gt;
There are &#039;&#039;multiple&#039;&#039; ways to exploit this, but the end-result for most of these is the same: overwrite code in AXIWRAM via the 0xEFF00000/0xDFF00000 kernel virtual-memory mapping.&lt;br /&gt;
&lt;br /&gt;
This was fixed in [[9.3.0-21|9.3.0-X]] by checking that the memchunk(including size, next, and prev ptrs) is located within the currently used heap memory. The kernel may also check that the next/prev ptrs are valid compared to other memchunk-headers basically. When any of these checks fail, kernelpanic() is called.&lt;br /&gt;
| When combined with other flaws: ARM11-kernelmode code execution&lt;br /&gt;
| [[9.3.0-21|9.3.0-21]]&lt;br /&gt;
| &lt;br /&gt;
| February 2014&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Multiple [[KLinkedListNode|KLinkedListNode]] SlabHeap use after free bugs&lt;br /&gt;
| The ARM11-kernel did access the &#039;key&#039; field of [[KLinkedListNode|KLinkedListNode]] objects, which are located on the SlabHeap, after freeing them. Thus, triggering an allocation of a new [[KLinkedListNode|KLinkedListNode]] object at the right time could result in a type-confusion. Pseudo-code:&lt;br /&gt;
SlabHeap_free(KLinkedListNode);&lt;br /&gt;
KObject *obj = KLinkedListNode-&amp;gt;key;  // the object there might have changed!&lt;br /&gt;
This bug appeared all over the place.&lt;br /&gt;
| ARM11-kernelmode code exec maybe&lt;br /&gt;
| [[8.0.0-18|8.0.0-18]]&lt;br /&gt;
| &lt;br /&gt;
| April 2015&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| PXI [[RPC_Command_Structure|Command]] input/output buffer permissions&lt;br /&gt;
| Originally the ARM11-kernel didn&#039;t check permissions for PXI input/output buffers for commands. Starting with [[6.0.0-11|6.0.0]] PXI input/output buffers must have RW permissions, otherwise kernelpanic is triggered.&lt;br /&gt;
| &lt;br /&gt;
| [[6.0.0-11|6.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcStartInterProcessDma]]&lt;br /&gt;
| For svcStartInterProcessDma, the kernel code had the following flaws:&lt;br /&gt;
&lt;br /&gt;
* Originally the ARM11-kernel read the input DmaConfig structure directly in kernel-mode(ldr(b/h) instructions), without checking whether the DmaConfig address is readable under userland. This was fixed by copying that structure to the SVC-mode stack, using the ldrbt instruction.&lt;br /&gt;
&lt;br /&gt;
* Integer overflows for srcaddr+size and dstaddr+size are now checked(with [[6.0.0-11]]), which were not checked before.&lt;br /&gt;
&lt;br /&gt;
* The kernel now also checks whether the srcaddr/dstaddr (+size) is within userland memory (0x20000000), the kernel now (with [[6.0.0-11]]) returns an error when the address is beyond userland memory. Using an address &amp;gt;=0x20000000 would result in the kernel reading from the process L1 MMU table, beyond the memory allocated for that MMU table(for vaddr-&amp;gt;physaddr conversion). &lt;br /&gt;
| &lt;br /&gt;
| [[6.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| DmaConfig issue: unknown. The rest: 2014&lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]] independently&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcControlMemory]] Parameter checks&lt;br /&gt;
| For svcControlMemory the parameter check had these two flaws:&lt;br /&gt;
&lt;br /&gt;
* The allowed range for addr0, addr1, size parameters depends on which MemoryOperation is being specified. The limitation for GSP heap was only checked if op=(u32)0x10003. By setting a random bit in op that has no meaning (like bit17?), op would instead be (u32)0x30003, and the range-check would be less strict and not accurate. However, the kernel doesn&#039;t actually use the input address for LINEAR memory-mapping at all besides the range-checks, so this isn&#039;t actually useful. This was fixed in the kernel by just checking for the LINEAR bit, instead of comparing the entire MemoryOperation value with 0x10003.&lt;br /&gt;
&lt;br /&gt;
* Integer overflows on (addr0+size) are now checked that previously weren&#039;t (this also applies to most other address checks elsewhere in the kernel).&lt;br /&gt;
&lt;br /&gt;
| &lt;br /&gt;
| [[5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| [[RPC_Command_Structure|Command]] request/response buffer overflow&lt;br /&gt;
| Originally the kernel did not check the word-values from the command-header. Starting with [[5.0.0-11]], the kernel will trigger a kernelpanic() when the total word-size of the entire command(including the cmd-header) is larger than 0x40-words (0x100-bytes). This allows overwriting threadlocalstorage+0x180 in the destination thread. However, since the data written there would be translate parameters (such as header-words + buffer addresses), exploiting this would likely be very difficult, if possible at all.&lt;br /&gt;
&lt;br /&gt;
If the two words at threadlocalstorage+0x180 could be overwritten with controlled data this way, one could then use a command with a buffer-header of &amp;lt;nowiki&amp;gt;((size&amp;lt;&amp;lt;14) | 2)&amp;lt;/nowiki&amp;gt; to write arbitrary memory to any RW userland memory in the destination process.&lt;br /&gt;
| &lt;br /&gt;
| [[5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| v4.1 FIRM -&amp;gt; v5.0 code diff&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|SVC stack allocation overflows]]&lt;br /&gt;
| &lt;br /&gt;
* Syscalls that allocate a variable-length array on stack, only checked bit31 before multiplying by 4/16 (when calculating how much memory to allocate). If a large integer was passed as input to one of these syscalls, an integer overflow would occur, and too little memory would have been allocated on stack resulting in a buffer overrun. &lt;br /&gt;
* The alignment (size+7)&amp;amp;~7 calculation before allocation was not checked for integer overflow.&lt;br /&gt;
&lt;br /&gt;
This might allow for ARM11 kernel code-execution.&lt;br /&gt;
&lt;br /&gt;
(Applies to svcSetResourceLimitValues, svcGetThreadList, svcGetProcessList, svcReplyAndReceive, svcWaitSynchronizationN.)&lt;br /&gt;
| &lt;br /&gt;
| [[5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| v4.1 FIRM -&amp;gt; v5.0 code diff&lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]] complementary&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcControlMemory]] MemoryOperation MAP memory-permissions&lt;br /&gt;
| svcControlMemory with MemoryOperation=MAP allows mapping the already-mapped process virtual-mem at addr1, to addr0. The lowest address permitted for addr1 is 0x00100000. Originally the ARM11 kernel didn&#039;t check memory permissions for addr1. Therefore .text as addr1 could be mapped elsewhere as RW- memory, which allowed ARM11 userland code-execution.&lt;br /&gt;
| &lt;br /&gt;
| [[4.1.0-8]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[RPC_Command_Structure|Command]] input/output buffer permissions&lt;br /&gt;
| Originally the ARM11 kernel didn&#039;t check memory permissions for the input/output buffers for commands. Starting with [[4.0.0-7]] the ARM11 kernel will trigger a kernelpanic() if the input/output buffers don&#039;t have the required memory permissions. For example, this allowed a FSUSER file-read to .text, which therefore allowed ARM11-userland code execution.&lt;br /&gt;
| &lt;br /&gt;
| [[4.0.0-7]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcReadProcessMemory/svcWriteProcessMemory memory]] permissions&lt;br /&gt;
| Originally the kernel only checked the first page(0x1000-bytes) of the src/dst buffers, for svcReadProcessMemory and svcWriteProcessMemory. There is no known retail processes which have access to these SVCs.&lt;br /&gt;
| &lt;br /&gt;
| [[4.0.0-7]]&lt;br /&gt;
| &lt;br /&gt;
| 2012?&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== [[FIRM]] Sysmodules ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[Services|&amp;quot;srv:pm&amp;quot;]] process registration&lt;br /&gt;
| Originally any process had access to the port &amp;quot;srv:pm&amp;quot;. The PID&#039;s used for the (un)registration commands are not checked either. This allowed any process to re-register itself with &amp;quot;srv:pm&amp;quot;, and therefore allowed the process to give itself access to any service, bypassing the exheader service-access-control list.&lt;br /&gt;
&lt;br /&gt;
This was fixed in [[7.0.0-13]]: starting with [[7.0.0-13]] &amp;quot;srv:pm&amp;quot; is now a service instead of a globally accessible port. Only processes with PID&#039;s less than 6 (in other words: fs, ldr, sm, pm, pxi modules) have access to it. With [[7.0.0-13]] there can only be one session for &amp;quot;srv:pm&amp;quot; open at a time(this is used by pm module), svcBreak will be executed if more sessions are opened by the processes which can access this.&lt;br /&gt;
&lt;br /&gt;
This flaw was needed for exploiting the &amp;lt;=v4.x Process9 PXI vulnerabilities from ARM11 userland ROP, since most applications don&#039;t have access to those service(s).&lt;br /&gt;
| Access to arbitrary services&lt;br /&gt;
| [[7.0.0-13]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| FSDIR null-deref&lt;br /&gt;
| [[Filesystem_services|FS]]-module may crash in some cases when handling directory reading. The trigger seems to be due to using [[FSDir:Close]] without closing the dir-handle afterwards?(Perhaps this is caused by out-of-memory?) This seems to be useless since it&#039;s just a null-deref.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| May 19(?)-20, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Useless [[SM]] off-by-one write&lt;br /&gt;
| After accepting a new session, [[SM]] writes a (handler ID (0 for srv: sessions (max. 64), 1 for the srv:pm one), pointer to session context structure in BSS) pair in a global array. However that array is only 64-entry-big instead of 65 (as it ought to be), and no bound check is done in that regard.&lt;br /&gt;
&lt;br /&gt;
Unfortunately, as of [[11.4.0-37]], the overwritten fields are totally unused after their initialization by &amp;lt;code&amp;gt;__libc_init_array&amp;lt;/code&amp;gt;.&lt;br /&gt;
| Not currently exploitable&lt;br /&gt;
| None&lt;br /&gt;
| [[11.4.0-37]]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| smpwn&lt;br /&gt;
| When registering a new service (or &amp;quot;port&amp;quot;), no bound checks are done on the service table. One can simply call RegisterPort repeatedly to overflow that table: it will overflow into the command replay structure.&lt;br /&gt;
&lt;br /&gt;
Combined with a other minor bugs in the sysmodule, it is possible to take over [[SM]] with this nevertheless difficult-to-exploit vulnerability.&lt;br /&gt;
| Code execution under [[SM]], etc.&lt;br /&gt;
| [[11.16.0-48]]&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| July 2017&lt;br /&gt;
| [[User:TuxSH|TuxSH]] (independently), presumably ichfly before &lt;br /&gt;
|-&lt;br /&gt;
| PXI cmdbuf buffer overrun &lt;br /&gt;
| Like its Arm9 counterpart, before version [[5.0.0-11|5.0.0-X]], the PXI system module did not check the command sizes. This makes it possible to get ROP under the PXI sysmodule from a pwned Process9.&lt;br /&gt;
safecerthax uses it to takeover the Arm11 processor after directly getting remote code execution on the Arm9 side. Though, is useless in classic Arm11 -&amp;gt; Arm9 chains.&lt;br /&gt;
| ROP under [[PXI_Services|PXI]]&lt;br /&gt;
| probably [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| &lt;br /&gt;
| Everyone&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Standalone Sysmodules ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in system-module system-version&lt;br /&gt;
!  Last system-module system-version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Timeframe this was added to wiki&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[CSND_Services|CSND]] sysmodule crash due to out of bounds parameters.&lt;br /&gt;
| The CSND command [[CSND:PlaySoundDirectly|PlaySoundDirectly (0x00040080)]] takes a channel ID as the first parameter. Any value outside the range [0-3] makes the system module become unstable or crash due to an out of bounds memory read. &lt;br /&gt;
| Out of bounds memory read, probably not exploitable. More research needed.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| January 2021&lt;br /&gt;
| January 22, 2021&lt;br /&gt;
| [[User:PabloMK7|PabloMK7]]&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| SSLoth: [[SSL_Services|SSL]] sysmodule improper certificate verification&lt;br /&gt;
| Initially, the SSL sysmodule missed the R_VERIFY_RES_SIGNATURE entry in the &amp;quot;resource list&amp;quot; provided to the RSA BSAFE library. Consequently, it did not check signatures when validating certificate chains. &lt;br /&gt;
| Forge fake certificates, spoof official servers and perform MitM attacks on SSL/TLS connections.&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| 2020&lt;br /&gt;
| December 18, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]], shutterbug2000 (independently)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD:ndm]] SetNZoneMacFilter (cmd8) stack smashing&lt;br /&gt;
| The length of the mac filter is not checked before being copied to a fixed-size buffer on stack.&lt;br /&gt;
| ROP under [[CECD_Services|CECD]] sysmodule&lt;br /&gt;
| None&lt;br /&gt;
| [[11.13.0-45]]&lt;br /&gt;
| 2020&lt;br /&gt;
| July 20, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] message box access &lt;br /&gt;
| CECD allows any process to write to any message box, thus allowing to write Streetpass data to the message box of any title.&lt;br /&gt;
| Install exploit for any title having a vulnerability in Streetpass data parsers (see CTRSDK Streetpass parser vulnerability).&lt;br /&gt;
| None&lt;br /&gt;
| None&lt;br /&gt;
| ?&lt;br /&gt;
| June 1, 2020&lt;br /&gt;
| Everyone?&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] packet type 0x32/0x34 stack-smashing&lt;br /&gt;
| When parsing Streetpass packets of type 0x32 and 0x34, CECD copies a list without checking the number of entries. The packet length is limited to 0x400 bytes, which is not enough to reach the end of the stack frame and overwrite the return address. However, the buffer located just next to the packet buffer is actually filled with data sent just before, hence actually allowing to overwrite the whole stack frame with conrolled data.&lt;br /&gt;
| RCE under [[CECD_Services|CECD]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| Summer 2019&lt;br /&gt;
| June 1, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] TMP files parser multiple vulnerabilities&lt;br /&gt;
| When parsing &amp;quot;TMP_XXX&amp;quot; files, CECD does not check the number of messages contained in the file. This allows to overflow the array of message pointers and message sizes on the stack. Pointers aren&#039;t controlled and sizes are limited (one cannot send gigabytes of data...), yet the last message size can be an arbitrary value (the current message pointer goes outside the file buffer and the parsing loop is broken). This allows to overwrite a pointer to a lock object on the stack and decrement an arbitrary value in memory. One can change the TMP file parsing mode to have CECD trying to free all the message buffers after parsing the next TMP file. The parsing mode is usually restored when parsing a new TMP file, but an invalid TMP file allows to make a function returns an error before the mode is restored , the return value is not checked and the parser consider the file valid. The message pointers and sizes arrays are not updated though, this is not a problem since the previous TMP file buffer is reused for the new TMP file in memory. Thus the message pointers actually points to controlled data. This allows to get a bunch of fake heap chunk freed, thus a bunch of unsafe unlink arbitrary writes.&lt;br /&gt;
| RCE under [[CECD_Services|CECD]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| Summer 2019&lt;br /&gt;
| June 1, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Config_Services|CFG]]:CreateConfigInfoBlk integer underflow&lt;br /&gt;
| When creating a new block it checks the size of the block is &amp;lt;= 0x8000, but it doesn&#039;t check that the block size is less than the remaining space. This induces an integer underflow (remaining_space-block_size), the result is then used for another check (buf_start+current_offset+constant &amp;lt;= remaining_space-block_size) and then in a mempcy call (dest = buf_start+(u16)(remaining_space-block_size), size =block_size). This allow for writing past the buffer, however because of the u16 cast in the memcpy call memory has to be mapped from buf_start to buf_start+0x10000 (cannot write backward).&lt;br /&gt;
| Theoritically ROP under CFG services, but BSS section is to small (size &amp;lt;= 0x10000) so it only results in a crash.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.8.0-41]]&lt;br /&gt;
| November, 2018&lt;br /&gt;
| November 24, 2018&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[MP:SendDataFrame]] missing input array index validation&lt;br /&gt;
| [[MP:SendDataFrame]] doesn&#039;t validate the input index at cmdreq[1], unless the function for flag=non-zero is executed. This is used to calculate the following, without validating the index at all: someptr = stateptr + (index*0x924) + somestateoffset.&lt;br /&gt;
&lt;br /&gt;
After validating some flags from someptr, when input_flag=0 the input buffer data is copied to someptr+someotheroffset+0x14 with the u16 size loaded from someptr+someotheroffset.&lt;br /&gt;
&lt;br /&gt;
With a large input index someptr could be setup to be at a &amp;lt;target address&amp;gt;, for overwriting memory.&lt;br /&gt;
&lt;br /&gt;
This is probably difficult to exploit.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[8.0.0-18]](MP-sysmodule v2048)&lt;br /&gt;
| January 22, 2017&lt;br /&gt;
| January 22, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[MP_Services|MP]] cmd1 out-of-bounds handle read&lt;br /&gt;
| MP-sysmodule handles the input parameter for cmd1 as a s32. It checks for &amp;gt;=16, but not &amp;lt;0. With &amp;lt;16 it basically does the following(array of entries 4-bytes each): *outhandle = ((Handle*)(stateptr+offsetinstate))[inputindex].&lt;br /&gt;
&lt;br /&gt;
Hence, this can be used to load any handle in MP-sysmodule memory. MP doesn&#039;t really have any service handles of interest however(can be obtained from elsewhere too).&lt;br /&gt;
| Reading any handle in MP-sysmodule memory.&lt;br /&gt;
| None&lt;br /&gt;
| [[8.0.0-18]](MP-sysmodule v2048)&lt;br /&gt;
| January 21, 2017&lt;br /&gt;
| January 22, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| AM stack/.bss infoleak via [[AM:ReadTwlBackupInfo]]([[AM:ReadTwlBackupInfoEx|Ex]])&lt;br /&gt;
| After writing the output-info structure to stack, it then copies that structure to the output buffer ptr using the size from the command. The size is not checked. This could be used to read data from the AM-service-thread stack handling the command + .bss.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;This was not tested on hardware.&#039;&#039;&#039;&lt;br /&gt;
| Stack/.bss reading&lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27]](AM v9217)&lt;br /&gt;
| Roughly October 17, 2016&lt;br /&gt;
| October 25, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| AM module APcert infoleak via 00000000.ctx files&lt;br /&gt;
| Just after a download title is purchased from the eShop, the .ctx is in an initialized state of all FFs past the header. During download, the FF area is filled with the console APcert. Thus, it is possible to create a xorpad from the initial state and use it to decrypt the APcert filled state.&lt;br /&gt;
| APcert contains the deviceID, which can beneficial in decrypting the movable.sed (since deviceID is mathmatically related to the LFCS).&lt;br /&gt;
| None&lt;br /&gt;
| [[11.16.0-49]]&lt;br /&gt;
| August, 2022&lt;br /&gt;
| March 17, 2023&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| [[MVD_Services|MVD]]: Stack buffer overflow with [[MVDSTD:SetupOutputBuffers]].&lt;br /&gt;
| The input total_entries is not validated when initially processing the input entry-list. This fixed-size input entry-list is copied to stack from the command request. The loop for processing this initializes a global table, the converted linearmem-&amp;gt;physaddrs used there are also copied to stack(0x8-bytes of physaddrs per entry).&lt;br /&gt;
&lt;br /&gt;
If total_entries is too large, MVD-sysmodule will crash due to reading unmapped memory following the stack(0x10000000). Afterwards if the out-of-bounds total_entries is smaller than that, it will crash due accessing address 0x0, hence this useless.&lt;br /&gt;
| MVD-sysmodule crash.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.0.0-20]]&lt;br /&gt;
| April 22, 2016 (Tested on the 25th)&lt;br /&gt;
| April 25, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWM_Services|NWM]]: Using CTRSDK heap with UDS sharedmem from the user-process.&lt;br /&gt;
| See the HTTP-sysmodule section below.&lt;br /&gt;
&lt;br /&gt;
CTRSDK heap is used with the sharedmem from [[NWMUDS:InitializeWithVersion]]. Buffers are allocated/freed under this heap using [[NWMUDS:Bind]] and [[NWMUDS:Unbind]].&lt;br /&gt;
&lt;br /&gt;
Hence, overwriting sharedmem with gspwn then using [[NWMUDS:Unbind]] results in the usual controlled CTRSDK memchunk-header write, similar to HTTP-sysmodule.&lt;br /&gt;
&lt;br /&gt;
This could be done by creating an UDS network, without any other nodes on the network.&lt;br /&gt;
&lt;br /&gt;
Besides CTRSDK memchunk-headers, there are no addresses stored under this sharedmem.&lt;br /&gt;
| ROP under NWM-module.&lt;br /&gt;
| None (need to check, but CTRSDK heap code is vulnerable)&lt;br /&gt;
| [[9.0.0-20|9.0.0-X]]&lt;br /&gt;
| April 10, 2016&lt;br /&gt;
| April 16, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[DLP_Services|DLP]]: Out-of-bounds memory access during spectator [[Download_Play|data-frame]] checksum calculation&lt;br /&gt;
| DLP doesn&#039;t validate the frame_size when receiving spectator data-frames at all, unlike non-spectator data-frames. The actual spectator data-frame parsing code doesn&#039;t use that field either. However, the data-frame checksum calculation code called during checksum verification does use the frame_size for loading the size of the framebuf.&lt;br /&gt;
&lt;br /&gt;
Hence, using a large frame_size like 0xFFFF will result in the checksum calculation code reading data out-of-bounds. This isn&#039;t really useful, you could trigger a remote local-WLAN DLP-sysmodule crash while a 3DS system is scanning for DLP networks(due to accessing unmapped memory), but that&#039;s about all(trying to infoleak with this likely isn&#039;t useful either).&lt;br /&gt;
| DLP-sysmodule crash, handled by dlplay system-application by a &amp;quot;connection interrupted&amp;quot; error eventually then a fatal-error via ErrDisp.&lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| April 8, 2016 (Tested on the 10th)&lt;br /&gt;
| April 10, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[DLP_Services|DLP]]: Out-of-bounds output data writing during spectator sysupdate titlelist [[Download_Play|data-frame]] handling&lt;br /&gt;
| The total_entries and out_entryindex fields for the titlelist DLP spectator data-frames are not validated. This is parsed during DLP network scanning. Hence, the specified titlelist data can be written out-of-bounds using the specified out_entryindex and total_entries. A crash will occur while reading the input data-frame titlelist if total_entries is larger than 0x27A, due to accessing unmapped memory.&lt;br /&gt;
&lt;br /&gt;
There&#039;s not much non-zero data to overwrite following the output buffer(located in sharedmem), any ptrs are located in sharedmem. Overwriting certain ptr(s) are only known to cause a crash when attempting to use the DLP-client shutdown service-command.&lt;br /&gt;
&lt;br /&gt;
There&#039;s no known way to exploit the above crash, since the linked-list code involves writes zeros(with a controlled start ptr).&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| April 8-9, 2016&lt;br /&gt;
| April 10, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[IR_Services|IR]]: Stack buffer overflow with custom hardware&lt;br /&gt;
| Originally IR sysmodule used the read value from the I2C-IR registers TXLVL and RXLVL without validating them at all. See [[10.6.0-31|here]] for the fix. This is the size used for reading the data-recv FIFO, etc. The output buffer for reading is located on the stack.&lt;br /&gt;
&lt;br /&gt;
This should be exploitable if one could successfully setup the custom hardware for this and if the entire intended sizes actually get read from I2C.&lt;br /&gt;
| ROP under IR sysmodule.&lt;br /&gt;
| [[10.6.0-31|10.6.0-31]]&lt;br /&gt;
| &lt;br /&gt;
| February 23, 2016 (Unknown if it was noticed before then)&lt;br /&gt;
| February 23, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[HTTP_Services|HTTP]]: Using CTRSDK heap with sharedmem from the user-process.&lt;br /&gt;
| The data from httpcAddPostDataAscii and other commands is stored under a CTRSDK heap. That heap is the sharedmem specified by the user-process via the HTTPC Initialize command.&lt;br /&gt;
Normally this sharedmem isn&#039;t accessible to the user-process once the sysmodule maps it, hence using it is supposed to be &amp;quot;safe&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This isn&#039;t the case due to gspwn however. Since CTRSDK heap code is so insecure in general, one can use gspwn to locate the HTTPC sharedmem + read/write it, then trigger a mem-write under the sysmodule. This can then be used to get ROP going under HTTP-sysmodule.&lt;br /&gt;
&lt;br /&gt;
This is exploited by [https://github.com/yellows8/ctr-httpwn/ctr-httpwn ctr-httpwn].&lt;br /&gt;
| ROP under HTTP sysmdule.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.13.0-45|11.13.0-X]]&lt;br /&gt;
| Late 2015&lt;br /&gt;
| March 22, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NIM_Services|NIM]]: Downloading old title-versions from eShop&lt;br /&gt;
| Multiple NIM service commands(such as [[NIMS:StartDownload]]) use a title-version value specified by the user-process, NIM does not validate that this input version matches the latest version available via SOAP. Therefore, when combined with AM(PXI) [[#Process9|title-downgrading]] via deleting the target eShop title with System Settings Data Management(if the title was already installed), this allows downloading+installing any title-version from eShop &#039;&#039;if&#039;&#039; it&#039;s still available from CDN.&lt;br /&gt;
The easiest way to exploit this is to just patch the eShop system-application code using these NIM commands(ideally the code which loads the title-version).&lt;br /&gt;
&lt;br /&gt;
Originally this was tested with a debugging-system via modded-FIRM, eventually smea implemented it in HANS for the 32c3 release.&lt;br /&gt;
| Downloading old title-versions from eShop&lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| October 24, 2015 (Unknown when exactly the first eShop title downgrade was actually tested, maybe November)&lt;br /&gt;
| January 7, 2016 (Same day Ironfall v1.0 was removed from CDN via the main-CXI files)&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SPI_Services|SPI]] service out-of-bounds write&lt;br /&gt;
| cmd1 has out-of-bounds write allowing overwrite of some static variables in .data.&lt;br /&gt;
| Code execution under spi sysmodule; access to [[CONFIG11_Registers|CFG11_GPUPROT]] and ultimately kernel code execution. &lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NFC_Services|NFC]] module service command buf-overflows&lt;br /&gt;
| NFC module copies data with certain commands, from command input buffers to stack without checking the size. These commands include the following, it&#039;s unknown if there&#039;s more commands with similar issues: &amp;quot;nfc:dev&amp;quot; &amp;lt;0x000C....&amp;gt; and &amp;quot;nfc:s&amp;quot; &amp;lt;0x0037....&amp;gt;.&lt;br /&gt;
Since both of these commands are stubbed in the Old3DS NFC module from the very first version(those just return an error), these issues only affect the New3DS NFC module.&lt;br /&gt;
&lt;br /&gt;
There&#039;s no known retail titles which have access to either of these services.&lt;br /&gt;
| ROP under NFC module.&lt;br /&gt;
| New3DS: None&lt;br /&gt;
| New3DS: [[9.5.0-22]]&lt;br /&gt;
| December 2014?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[News_Services|NEWSS]] service command notificationID validation failure&lt;br /&gt;
| This module does not validate the input notificationID for &amp;lt;nowiki&amp;gt;&amp;quot;news:s&amp;quot;&amp;lt;/nowiki&amp;gt; service commands. This is an out-of-bounds array index bug. For example, [[NEWSS:SetNotificationHeader]] could be used to exploit news module: this copies the input data(size is properly checked) to: out = newsdb_savedata+0x10 + (someu32array[notificationID]*0x70).&lt;br /&gt;
| ROP under news module.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.7.0-25|9.7.0-X]]&lt;br /&gt;
| December 2014&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWMUDS:DecryptBeaconData]] heap buffer overflow&lt;br /&gt;
| input_size = 0x1E * &amp;lt;value the u8 from input_[[NWM_Services|networkstruct]]+0x1D&amp;gt;. Then input_tag0 is copied to a heap buffer. When input_size is larger than 0xFA-bytes, it will then copy input_tag1 to &amp;lt;end_address_of_previous_outbuf&amp;gt;, with size=input_size-0xFA.&lt;br /&gt;
&lt;br /&gt;
This can be triggered by either using this command directly, or by boadcasting a wifi beacon which triggers it while a 3DS system running the target process is in range, when the process is scanning for hosts to connect to. Processes will only pass tag data to this command when the wlancommID and other thing(s) match the values for the process.&lt;br /&gt;
&lt;br /&gt;
There&#039;s no known way to actually exploit this for getting ROP under NWM-module, at the time of originally adding this to the wiki. This is because the data which gets copied out-of-bounds *and* actually causes crash(es), can&#039;t be controlled it seems(with just broadcasting a beacon at least). It&#039;s unknown whether this could be exploited from just using NWMUDS service-cmd(s) directly.&lt;br /&gt;
| Without any actual way to exploit this: NWM-module DoS, resulting in process termination(process crash). This breaks *everything* involving wifi comms, a reboot is required to recover from this.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.0.0-20]]&lt;br /&gt;
| ~September 23, 2014(see the [[NWMUDS:DecryptBeaconData]] page history)&lt;br /&gt;
| August 3, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[HID_Services|HID]] module shared-mem&lt;br /&gt;
| HID module does not validate the index values in [[HID_Shared_Memory|sharedmem]](just changes index to 0 when index == maxval when updating), therefore large values will result in HID module writing HID data to arbitrary addresses.&lt;br /&gt;
| ROP under HID module, but this is *very* unlikely to be exploitable since the data written is HID data.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.3.0-21]]&lt;br /&gt;
| 2014?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| gspwn&lt;br /&gt;
| GSP module does not validate addresses given to the GPU. This allows a user-mode application/applet to read/write to a large part of physical FCRAM using GPU DMA. From this, you can overwrite the .text segment of the application you&#039;re running under, and gain real code-execution from a ROP-chain. Normally applets&#039; .text([[Home Menu]], [[Internet Browser]], etc) is located beyond the area accessible by the GPU, except for [[RO_Services|CROs]] used by applets([[Internet Browser]] for example).&lt;br /&gt;
&lt;br /&gt;
FCRAM is gpu-accessible up to physaddr 0x26800000 on Old3DS, and 0x2D800000 on New3DS. This is BASE_memregion_start(aka SYSTEM_memregion_end)-0x400000 (0x800000 with New3DS) with the default memory-layout on Old3DS/New3DS. With [[11.3.0-36|11.3.0-X]] the cutoff now varies due to the new [[SVC]] 0x59. The New3DS &amp;quot;normal&amp;quot;(non-APPLICATION) cutoff was changed to 0x2D000000 due to the new [[SVC]] 0x59.&lt;br /&gt;
| User-mode code execution.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| Early 2014&lt;br /&gt;
| &lt;br /&gt;
| smea, [[User:Yellows8|Yellows8]]/others before then&lt;br /&gt;
|-&lt;br /&gt;
| [[GSP_Services|GSP]]: client management failures&lt;br /&gt;
| Shared memory of GSP clients is all on the same page, this allows any GSP client to craft custom GX commands for other clients. Additionally, [[GSPGPU:TriggerCmdReqQueue]] does not check if the calling client has rendering rights.&lt;br /&gt;
&lt;br /&gt;
These two flaws can be used to craft DMA/Transfer Engine commands within a different GSP client to issue reads/writes to both physical (akin to gspwn) and virtual memory of said client.&lt;br /&gt;
| Arbitrary RW from and into a client process.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.17.0-50|11.17.0-50]]&lt;br /&gt;
|&lt;br /&gt;
| May 2025&lt;br /&gt;
| [[User:kynex7510|kynex7510]], probably others&lt;br /&gt;
|-&lt;br /&gt;
| [[GSP_Services|GSP]]: unbound DMA&lt;br /&gt;
| GSP doesn&#039;t really care what process handle is passed to [[GSPGPU:AcquireRight]]. Hence, it&#039;s possible to craft DMA commands to read/write within that process virtual address space.&lt;br /&gt;
| Arbitrary RW within any process.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.17.0-50|11.17.0-50]]&lt;br /&gt;
|&lt;br /&gt;
| June 2025&lt;br /&gt;
| [[User:kynex7510|kynex7510]]&lt;br /&gt;
|-&lt;br /&gt;
| rohax&lt;br /&gt;
| Using gspwn, it is possible to overwrite a loaded [[CRO0]]/[[CRR0]] after its RSA-signature has been validated. Badly validated [[CRO0]] header leads to arbitrary read/write of memory in the ro-process. This gives code-execution in the ro module, who has access to [[SVC|syscalls]] 0x70-0x72, 0x7D.&lt;br /&gt;
&lt;br /&gt;
This was fixed after [[ninjhax]] release by adding checks on [[CRO0]]-based pointers before writing to them.&lt;br /&gt;
| Memory-mapping syscalls.&lt;br /&gt;
| [[9.3.0-21]]&lt;br /&gt;
| [[9.4.0-21]]&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
| smea, [[User:Plutooo|plutoo]] joint effort&lt;br /&gt;
|-&lt;br /&gt;
| [[RO_Services|RO]]: custom CRO mapping into any process&lt;br /&gt;
| RO stores pointers to previously loaded CRRs in internal state, however it doesn&#039;t keep track to which process they belong to. Thus pointers can be reused among different processes, and since CRR verification only happens on load this bypasses it.&lt;br /&gt;
&lt;br /&gt;
Granted a handle to the target process is available, the following strategy can be used to load a custom CRO into any process:&lt;br /&gt;
&lt;br /&gt;
* Map valid CRS, CRR into the current application, and initialize RO normally;&lt;br /&gt;
* Write custom CRR, CRO into the target process at the same addresses (process handle can be used with GSP DMA capabilities for read/write operations, see above);&lt;br /&gt;
* Load CRO into the target process using its handle.&lt;br /&gt;
&lt;br /&gt;
| Code execution in the target process.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.17.0-50|11.17.0-50]]&lt;br /&gt;
| &lt;br /&gt;
| June 2025&lt;br /&gt;
| [[User:kynex7510|kynex7510]]&lt;br /&gt;
|-&lt;br /&gt;
| Region free&lt;br /&gt;
| Only [[Home Menu]] itself checks gamecards&#039; region when launching them. Therefore, any application launch that is done directly with [[NS]] without signaling Home Menu to launch the app, will result in region checks being bypassed.&lt;br /&gt;
This essentially means launching the gamecard with the [[NS_and_APT_Services|&amp;quot;ns:s&amp;quot;]] service. The main way to exploit this is to trigger a FIRM launch with an application specified, either with a normal FIRM launch or a hardware [[NSS:RebootSystem|reboot]].&lt;br /&gt;
| Launching gamecards from any region + bypassing Home Menu gamecard-sysupdate installation&lt;br /&gt;
| None&lt;br /&gt;
| Last tested with [[10.1.0-27|10.1.0-X]].&lt;br /&gt;
| June(?) 2014&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWM_Services|NWM]] service-cmd state null-ptr deref&lt;br /&gt;
| The NWMUDS service command code loads a ptr from .data, adds an offset to that, then passes that as the state address for the actual command-handler function. The value of the ptr loaded from .data is not checked, therefore this will cause crashes due to that being 0x0 when NWMUDS was not properly initialized.&lt;br /&gt;
It&#039;s unknown whether any NWM services besides NWMUDS have this issue.&lt;br /&gt;
| This is rather useless since it&#039;s only a crash caused by a state ptr based at 0x0.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.0.0-20]]&lt;br /&gt;
| 2013?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== General/CTRSDK ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in version&lt;br /&gt;
!  Last version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] Streetpass message exheader stack-smashing&lt;br /&gt;
| When parsing streetpass messages, &amp;quot;nn::cec::CTR::Message::InputMessage&amp;quot; calls &amp;quot;nn::cec::CTR::Message::SetExHeaderWithoutCalc&amp;quot; for each exheader entry in the input message. The number of entries should not exceed 16 but remains unchecked, leading to a stack-buffer-overflow.&lt;br /&gt;
| ROP under any application parsing Streetpass messages&lt;br /&gt;
Remote code execution under [[CECD_Services|CECD]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| &lt;br /&gt;
| 2019&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWM_Services|UDS]] beacon additional-data buffer overflow&lt;br /&gt;
| Originally CTRSDK did not validate the UDS additional-data size before using that size to copy the additional-data to a [[NWM_Services|networkstruct]]. This was eventually fixed.&lt;br /&gt;
This was discovered while doing code RE with an old dlp-module version. It&#039;s unknown in what specific CTRSDK version this was fixed, or even what system-version updated titles with a fixed version.&lt;br /&gt;
&lt;br /&gt;
It&#039;s unknown if there&#039;s any titles using a vulnerable CTRSDK version which are also exploitable with this(dlp module can&#039;t be exploited with this).&lt;br /&gt;
&lt;br /&gt;
The maximum number of bytes that can be written beyond the end of the outbuf is 0x37-bytes, with additionaldata_size=0xFF.&lt;br /&gt;
| Perhaps ROP, very difficult if possible with anything at all&lt;br /&gt;
| ?&lt;br /&gt;
| &lt;br /&gt;
| September(?) 2014&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| CTPK buffer overflow&lt;br /&gt;
| At offset 0x20 in CTPK is an array for each texture, each entry is 0x20-bytes. This contains a wordindex(entry+0x18) for some srcdata relative to CTPK+0, and an u8 wordsize(entry+0x14) for this data. The CTRSDK function handling this doesn&#039;t validate the size, when copying srcdata using this size to the output buffer. Applications usually have the output buffer on the stack, hence stack buffer overflow.&lt;br /&gt;
&lt;br /&gt;
While CTPK(*.ctpk) are normally only loaded from RomFS, some application(s) load from elsewhere too.&lt;br /&gt;
| ROP under the target application.&lt;br /&gt;
| None?&lt;br /&gt;
| &amp;quot;[SDK+NINTENDO:CTR_SDK-11_4_0_200_none]&amp;quot;&lt;br /&gt;
| November 14, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Pia vulns&lt;br /&gt;
| [https://switchbrew.org/wiki/Switch_System_Flaws#Pia Originally discovered in Pia v5.x for Switch], these vulns are also present in earlier versions (v3.x/4.x/5.x, possibly earlier?) for 3DS (and Wii U too).&lt;br /&gt;
Pia encryption generally wasn&#039;t used pre-Switch (sent packets are plaintext). 3DS is affected by all Pia vulns listed above except for LAN. The functionality for ParseLeaveMeshInvitation doesn&#039;t exist in 3DS Pia v3.9.2. Wii U is affected by all listed Pia vulns except for the LAN vulns.&lt;br /&gt;
| See [https://switchbrew.org/wiki/Switch_System_Flaws#Pia here].&lt;br /&gt;
| Unfixed on 3DS/Wii U&lt;br /&gt;
| &amp;quot;[SDK+Nintendo:PIA_5_4_3]&amp;quot;&lt;br /&gt;
| See [https://switchbrew.org/wiki/Switch_System_Flaws#Pia here]; separately checked later (UpdateConnectionReport) by [[User:Riley|Riley]] on: June 14, 2023&lt;br /&gt;
| [[User:Yellows8|Yellows8]]; added to 3dbrew (UpdateConnectionReport) by [[User:Riley|Riley]] later&lt;br /&gt;
|-&lt;br /&gt;
| pialease nerf: stack overflow in Pia when parsing UDS packet cmd=5 &amp;quot;UpdateMigrationNodeInfoMessage&amp;quot;&lt;br /&gt;
| A UDS packet as received by Pia contains a command type, where cmd=1 is higher-layer game-data, and other cmds are parsed internally.&lt;br /&gt;
&lt;br /&gt;
A function named &amp;quot;UdsNode::ParseUpdateMigrationNodeInfoMessage&amp;quot; is called to handle packets with cmd=5.&lt;br /&gt;
&lt;br /&gt;
This checks the player nodeID (returns if not player 1, that is, UDS network host), then calls an additional function which does a loop of 64-bit copies to a fixed-size stack buffer using unchecked index and data from the received packet contents.&lt;br /&gt;
&lt;br /&gt;
This therefore leads to trivial RCE (of every UDS network client) by just sending a single UDS packet; only 0xC u64s on stack can be overwritten easily, but just 2 is enough to start a ROP chain and pivot to the rest of the UDS packet contents elsewhere on the stack.&lt;br /&gt;
&lt;br /&gt;
To exploit some games, an attacker would need to also reimplement the DLP server protocol (and any quirks that game has when parsing the UDS network passphrase obtained from the DLP server). One game that requires this is Mario Party: Island Tour (only the dlplay child connects to a UDS network).&lt;br /&gt;
&lt;br /&gt;
Earliest version of Pia known to be vulnerable is v2.x. v1.x still parses this packet, but does not have the stack-copy loop (index is still unchecked there leading to heap overflow but due to overwrites not being contiguous in memory it may or may not be exploitable).&lt;br /&gt;
&lt;br /&gt;
Fixed with Pia version 4.x, which refactored the UDS send/receive wrapper code and parses completely different commands.&lt;br /&gt;
| ROP under the vulnerable application. A server can exploit every client connected to it; a client can exploit every other client connected to that server.&lt;br /&gt;
| &amp;quot;[SDK+Nintendo:PIA_4_2_0]&amp;quot;&lt;br /&gt;
| &amp;quot;[SDK+Nintendo:PIA_3_10_2]&amp;quot;, &amp;quot;[SDK+Nintendo:PIA_4_2_0]&amp;quot;&lt;br /&gt;
| Discovery: June 3, 2023.&lt;br /&gt;
&lt;br /&gt;
Wiki: November 20, 2023.&lt;br /&gt;
| [[User:Riley|Riley]]&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23586</id>
		<title>GPU/External Registers</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23586"/>
		<updated>2025-06-19T17:09:11Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: PPF rewrites + add T2L stuff&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the address range accessible from the ARM11, used to configure the basic GPU functionality. For information about the internal registers used for 3D rendering, see [[GPU/Internal Registers]].&lt;br /&gt;
&lt;br /&gt;
== Map ==&lt;br /&gt;
Address mappings for the external registers. GSPGPU:WriteHWRegs takes these addresses relative to 0x1EB00000. &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! User VA&lt;br /&gt;
! PA&lt;br /&gt;
! Length&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00000&lt;br /&gt;
| 0x10400000&lt;br /&gt;
| 4&lt;br /&gt;
| Hardware ID&lt;br /&gt;
| Bit2: new model&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00004&lt;br /&gt;
| 0x10400004&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00010&lt;br /&gt;
| 0x10400010&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill1]] &amp;quot;PSC0&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00020&lt;br /&gt;
| 0x10400020&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill2]] &amp;quot;PSC1&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00030&lt;br /&gt;
| 0x10400030&lt;br /&gt;
| 4&lt;br /&gt;
| VRAM bank control&lt;br /&gt;
| Bits 8-11 = bank[i] disabled; other bits are unused.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00034&lt;br /&gt;
| 0x10400034&lt;br /&gt;
| 4&lt;br /&gt;
| GPU Busy&lt;br /&gt;
| Bit26 = PSC0, bit27 = PSC1, Bit30 = PPF, Bit31 = P3D&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00050&lt;br /&gt;
| 0x10400050&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x22221200 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00054&lt;br /&gt;
| 0x10400054&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFF2 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000C0&lt;br /&gt;
| 0x104000C0&lt;br /&gt;
| 4&lt;br /&gt;
| Backlight control&lt;br /&gt;
| Writes 0x0 to allow backlights to turn off, 0x20000000 to force them always on.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00400&lt;br /&gt;
| 0x10400400&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC0&amp;quot; (top screen)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00500&lt;br /&gt;
| 0x10400500&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC1&amp;quot; (bottom)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| 0x10400C00&lt;br /&gt;
| ?&lt;br /&gt;
| [[#Transfer_Engine|Transfer Engine]] &amp;quot;DMA&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|colspan=&amp;quot;5&amp;quot;| 0x1EF01000/0x10401000 - 0x1EF01C00/0x10401C00 maps to [[GPU/Internal_Registers|GPU internal registers]]. These registers are usually not read/written directly here, but are written using the command list interface below (corresponding to the GPUREG_CMDBUF_* internal registers)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01000&lt;br /&gt;
| 0x10401000&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0 on GPU init and before the Command List is used&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01080&lt;br /&gt;
| 0x10401080&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x12345678 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010C0&lt;br /&gt;
| 0x104010C0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFFFFFFF0 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010D0&lt;br /&gt;
| 0x104010D0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 1 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF014??&lt;br /&gt;
| 0x104014??&lt;br /&gt;
| 0x14&lt;br /&gt;
| &amp;quot;PPF&amp;quot; ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| 0x104018E0&lt;br /&gt;
| 0x14&lt;br /&gt;
| [[#Command_List|Command List]] &amp;quot;P3D&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Memory Fill ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  User VA&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X0&lt;br /&gt;
| Buffer start physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X4&lt;br /&gt;
| Buffer end physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X8&lt;br /&gt;
| Fill value&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000XC&lt;br /&gt;
| Control. bit0: start/busy, bit1: finished, bit8-9: fill-width (0=16bit, 1=3=24bit, 2=32bit)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Memory fills are used to initialize buffers in memory with a given value, similar to memset. A memory fill is triggered by setting bit0 in the control register. Doing so aborts any running memory fills on that filling unit. Upon completion, the hardware unsets bit0 and sets bit1 and fires interrupt PSC0.&lt;br /&gt;
&lt;br /&gt;
The addresses must be part of VRAM.&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP Shared Memory#GX SetMemoryFill|GX SetMemoryFill]].&lt;br /&gt;
&lt;br /&gt;
== LCD Source Framebuffer Setup ==&lt;br /&gt;
&lt;br /&gt;
All of these registers must be accessed with 32bit operations regardless of the registers&#039; actual bit size.&lt;br /&gt;
&lt;br /&gt;
The naming of these parameters reflects the physical characteristics of the displays, and not the way the 3DS is normally held.&lt;br /&gt;
&lt;br /&gt;
To make sense of these values, the 3DS must be held in a way, so that the bottom screen is in the left hand, and the top screen is in the right hand, and that way the first pixel will be in the top-left corner, as it should be. If the 3DS is held normally, the first pixel is in the bottom-left corner.&lt;br /&gt;
&lt;br /&gt;
All pixel and scanline timing values are 12bits, unless noted. This also applies to those fields where two u16 are combined into one register. Each u16 field is only 12bits in size. timin&lt;br /&gt;
&lt;br /&gt;
The horizontal timing parameter order is as follows (values may overflow through HTotal register value):&lt;br /&gt;
 0x10 &amp;lt; 0x14 &amp;lt;= 0x60.LO &amp;lt;= 0x04 &amp;lt;= 0x60.HI &amp;lt;= 0x08 &amp;lt;= 0x0C &amp;lt;= 0x10&lt;br /&gt;
 0x18 &amp;lt;= 0x60.LO&lt;br /&gt;
&lt;br /&gt;
Timing starts from HCount == 0, then each absolute value in the beforementioned register chain triggers when HCount == register, latching the primitive display controller into a new mode.&lt;br /&gt;
There is an inherent latch order, where if two simultenaous events occur, one event wins over another.&lt;br /&gt;
&lt;br /&gt;
 Known latched modes (in order):&lt;br /&gt;
 - HSync (triggers a line to the LCD to move to the next line)&lt;br /&gt;
 - Back porch (area between HSync and border being displayed, no pixels pushed, min 16 pixel clocks, otherwise the screen gets glitchy)&lt;br /&gt;
 - Left border start (no image data is being displayed, just a configurable solid color)&lt;br /&gt;
 - Image start (pixel data is being DMA&#039;d from video memory or main RAM)&lt;br /&gt;
 - Right border start/Image end (border color is being displayed after the main image)&lt;br /&gt;
 - Unknown synchronization (supposed to be probably right border end, but this mode seems to be broken or not do anything)&lt;br /&gt;
 - Front porch (no pixels pushed, 68 clock min, otherwise the screen doesn&#039;t sync properly, and really glitches out)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Offset&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| HTotal&lt;br /&gt;
| The total width of a timing scanline. In other words, this is the horizontal refresh clock divider value.&lt;br /&gt;
&lt;br /&gt;
HClock = PClock / (HTotal + 1)&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| HStart&lt;br /&gt;
| Determines when the image is going to be displayed in the visible region (register 0x60).&lt;br /&gt;
|-&lt;br /&gt;
| 0x08&lt;br /&gt;
| HBR&lt;br /&gt;
| Right border start(?). Does nothing.&lt;br /&gt;
&lt;br /&gt;
While this register seems to have no impact on the image whatsoever, it still has to be set to a valid value.&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| 0x0C&lt;br /&gt;
| HPF&lt;br /&gt;
| Front porch. The image is blanked during this period, and no pixels are pushed to the LCD.&lt;br /&gt;
&lt;br /&gt;
Unknown why, but a single dot of red is displayed before entering this mode.&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| HSync&lt;br /&gt;
| Triggers a HSync pulse.&lt;br /&gt;
&lt;br /&gt;
Based on behavior, this needs to last at least a pixel clock for the LCD to register the sync.&lt;br /&gt;
|-&lt;br /&gt;
| 0x14&lt;br /&gt;
| HPB&lt;br /&gt;
| Back porch? Has to be at least one bigger than HSync, otherwise HSync never triggers.&lt;br /&gt;
&lt;br /&gt;
The display is blank, and the LCD displays nothing in this period (doesn&#039;t push pixels).&lt;br /&gt;
|-&lt;br /&gt;
| 0x18&lt;br /&gt;
| HBL&lt;br /&gt;
| Left border trigger treshold. Enables pushing pixels to the display.&lt;br /&gt;
&lt;br /&gt;
If this value is smaller than the back porch, then the back porch period will be zero, and the border will be immediately displayed upon entering the back porch period.&lt;br /&gt;
&lt;br /&gt;
Can be lower than HSync, as the back porch is what takes the controller out of HSync.&lt;br /&gt;
&lt;br /&gt;
Must be &amp;lt;= HDisp start (reg 0x60 low u16), otherwise no pixels will be pushed due to a glitched state.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1C&lt;br /&gt;
| H Interrupt timing&lt;br /&gt;
| Made up from two u16 values, PDC interrupt line is asserted when HCount == low u16, and most likely deasserted when HCount == high u16.&lt;br /&gt;
&lt;br /&gt;
There seems to be some limitations though:&lt;br /&gt;
* low u16 must be smaller than high u16&lt;br /&gt;
* if low u16 is less than HTotal then high u16 must also be smaller than HTotal&lt;br /&gt;
* setting low u16 to &amp;gt;= HTotal disables the interrupt ever firing&lt;br /&gt;
&lt;br /&gt;
This is configured by gsp in a way so that low u16 equals to HTotal, meaning the HSync interrupt will never fire.&lt;br /&gt;
|-&lt;br /&gt;
| 0x20&lt;br /&gt;
| low u16: ???&lt;br /&gt;
high u16: ???&lt;br /&gt;
| ???&lt;br /&gt;
|-&lt;br /&gt;
| 0x24&lt;br /&gt;
| VTotal&lt;br /&gt;
| Total height of the timing window. Can be interpreted as the vertical clock divider.&lt;br /&gt;
&lt;br /&gt;
VClock = PClock / (HTotal + 1) / (VTotal + 1)&lt;br /&gt;
&lt;br /&gt;
Setting this to 494 lowers framerate to about 50.040660858 Hz ((268111856 / 24) / (450 + 1) / (494 + 1)).&lt;br /&gt;
|-&lt;br /&gt;
| 0x28&lt;br /&gt;
| ?&lt;br /&gt;
| Seems to determine the vertical blanking interval.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Setting this to lower than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will cut off the top &amp;lt;code&amp;gt;VTotal - VDisp - thisvalue&amp;lt;/code&amp;gt; lines.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will make the image be pushed downwards with the overscan color visible.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;HTotal&amp;lt;/code&amp;gt; will make the GPU skip vertical pixel data synchronization (hence filling the screen with the rest of the pixel data past the given screen framebuffer size). Also will skip &amp;lt;code&amp;gt;thisvalue + somevalue - HTotal&amp;lt;/code&amp;gt; lines into the &amp;quot;global&amp;quot; pixel buffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x30&lt;br /&gt;
| ?&lt;br /&gt;
| Total amount of vertical scanlines in the pixel buffer, must be bigger than *an unknown blanking-like value*. If this value is less than VDisp then the last two scanlines will be repeated interlaced until VDisp is reached.&lt;br /&gt;
|-&lt;br /&gt;
| 0x34&lt;br /&gt;
| VDisp(?)&lt;br /&gt;
| Total amonut of vertical scanlines displayed (only for top screen it seems like). If this value is less than VTotal then the rest of the scanlines will not be updated on the screen, so those will slowly fade out. Must be bigger than *an unknown blanking-like value*, otherwise an underflow will happen.&lt;br /&gt;
|-&lt;br /&gt;
| 0x38&lt;br /&gt;
| Vertical data offset(?)&lt;br /&gt;
| ??? Seems to offset the screen upwards if this value is high enough. If this value is higher or equal to *some value* (aka. if less than one scanline is displayed on the screen) then the screen will lose synchronization.&lt;br /&gt;
|-&lt;br /&gt;
| 0x40&lt;br /&gt;
| V Interrupt timing&lt;br /&gt;
| Similar to H Interrupt timing (0x1C), except the comparison is done against VCount, the limitations are emposed on VTotal, and the interrupt that fires is VSync.&lt;br /&gt;
&lt;br /&gt;
One important note is that it seems like the VSync interrupt always fires at HCount == 0, and there doesn&#039;t seem to be a register to control this behavior.&lt;br /&gt;
|-&lt;br /&gt;
| 0x44&lt;br /&gt;
| ???&lt;br /&gt;
| similar functionality to 0x10&lt;br /&gt;
|-&lt;br /&gt;
| 0x48&lt;br /&gt;
| ???&lt;br /&gt;
| bit0 seems to disable HSync, bit8 seems to disable VSync, rest of the bits aren&#039;t writable.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4C&lt;br /&gt;
| Overscan filler color&lt;br /&gt;
| 24bits(? top 8bits ignored)&lt;br /&gt;
&lt;br /&gt;
When the visible region is being drawn, but the timing parameters are set up in a way that the framebuffer is smaller than the visible region, it will be filled by this color.&lt;br /&gt;
|-&lt;br /&gt;
| 0x50&lt;br /&gt;
| HCount&lt;br /&gt;
| Horizontal &amp;quot;beam position&amp;quot; counter. Note that this value does not equal to the current pixel being drawn.&lt;br /&gt;
|-&lt;br /&gt;
| 0x54&lt;br /&gt;
| VCount&lt;br /&gt;
| Vertical &amp;quot;beam position&amp;quot; counter. Note that the scanline being drawn isn&#039;t equal to this value.&lt;br /&gt;
|-&lt;br /&gt;
| 0x5C&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: Image width (including some offset?)&lt;br /&gt;
high u16: Image height??? (seems to be unused)&lt;br /&gt;
|-&lt;br /&gt;
| 0x60&lt;br /&gt;
| HDisp&lt;br /&gt;
| low u16: Image start (border --&amp;gt; pixel data)&lt;br /&gt;
high u16: Image end (pixel data --&amp;gt; border)&lt;br /&gt;
|-&lt;br /&gt;
| 0x64&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: unknown&lt;br /&gt;
high u16: framebuffer total height (amount of scanlines blitted regardless of framebuffer height)&lt;br /&gt;
|-&lt;br /&gt;
| 0x68&lt;br /&gt;
| Framebuffer A first address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x6C&lt;br /&gt;
| Framebuffer A second address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x70&lt;br /&gt;
| Framebuffer format and other settings&lt;br /&gt;
| See [[#Framebuffer_format|framebuffer format]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x74&lt;br /&gt;
| PDC control&lt;br /&gt;
| Bit 0: Enable display controller.&lt;br /&gt;
Bit 8: HBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 9: VBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 10: Error IRQ mask? (0 = enabled).&lt;br /&gt;
Bit 16: Output enable?&lt;br /&gt;
|-&lt;br /&gt;
| 0x78&lt;br /&gt;
| Framebuffer select and status&lt;br /&gt;
| Bit 0: Next framebuffer to display (after VBlank).&lt;br /&gt;
Bit 4: Currently displaying framebuffer?&lt;br /&gt;
Bit 8: Reset FIFO?&lt;br /&gt;
Bit 16: HBlank IRQ status/ack. Write 1 to aknowledge.&lt;br /&gt;
Bit 17: VBlank IRQ status/ack.&lt;br /&gt;
Bit 18: Error IRQ status/ack?&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| Color lookup table index select&lt;br /&gt;
| 8bits, write-only&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| Color lookup table indexed element&lt;br /&gt;
| Contains the value of the color lookup table indexed by the above register, 24bits, RGB8 (0x00BBGGRR)  &lt;br /&gt;
Accessing this register will increase the index register by one&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| Framebuffer stride&lt;br /&gt;
| 32bits (bottom 3bits ignored?)&lt;br /&gt;
&lt;br /&gt;
Distance in bytes between the start of two framebuffer rows (must be a multiple of 8).&lt;br /&gt;
&lt;br /&gt;
In other words, this can be interpreted as the amount to add to the framebuffer pointer after displaying a scanline.&lt;br /&gt;
&lt;br /&gt;
Setting this to zero will cause only the first line of the image to be displayed repeated on the entire display. With the HSync interrupt it&#039;s possible to &amp;quot;race the beam&amp;quot; to (ab)use this feature.&lt;br /&gt;
&lt;br /&gt;
Because of this simplicity, writing a negative value here VFlips the image, although that requires the framebuffer pointer register to be set to the start of the last scanline, instead of at the start of the framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| Framebuffer B first address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| Framebuffer B second address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer format ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 2-0&lt;br /&gt;
| [[#Framebuffer_color_formats|Color format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5-4&lt;br /&gt;
| Framebuffer interlacing mode&lt;br /&gt;
&lt;br /&gt;
 0 - A  (no interlacing)&lt;br /&gt;
 1 - AA (scanline doubling)&lt;br /&gt;
 2 - AB (interlace enable)&lt;br /&gt;
 3 - BA (same as above, but the fields are inverted)&lt;br /&gt;
&lt;br /&gt;
In AB and BA interlace modes, a scanline from each framebuffer is output in an alternating manner. In AB mode, Framebuffer A is output on the frist display scanline. Similarly, in BA mode, Framebuffer B gets output to the first display scanline.&lt;br /&gt;
&lt;br /&gt;
The way AB and BA modes work, is that a scanline is output, the framebuffer stride value is added to the internal scanline pointer value, and the other framebuffer is selected. And this alternates until the end of the draw region.&lt;br /&gt;
&lt;br /&gt;
AA interlacing works like AB interlacing, except both internal framebuffer pointers are set to the Framebuffer A pointer value.&lt;br /&gt;
&lt;br /&gt;
In A mode (no interlacing), it doesn&#039;t switch to the other framebuffer at the end of outpuitting a scanline to the display.&lt;br /&gt;
&lt;br /&gt;
Bottom screen has this set to 0 (A mode, no interlacing) at all times.  &lt;br /&gt;
Top screen uses AB interlacing in 3D mode (with 3D slider enabled), and A mode (no interlacing) in 2D mode.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Alternative pixel output mode*&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 9-8&lt;br /&gt;
| DMA size&lt;br /&gt;
&lt;br /&gt;
 0 -  4 FCRAM words (32 bytes)&lt;br /&gt;
 1 -  8 FCRAM words (64 bytes)&lt;br /&gt;
 2 - 16 FCRAM words (128 bytes)&lt;br /&gt;
 3 - ???&lt;br /&gt;
&lt;br /&gt;
FCRAM doesn&#039;t support DMA size 3, as it can only burst up to 16 words (128 bytes), and will show a black screen instead.&lt;br /&gt;
|-&lt;br /&gt;
| 31-16&lt;br /&gt;
| Unknown&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt; The weird thing about bit6, is that it works different between the bottom and top LCD. On the bottom LCD, it doubles the number of outputted pixels (so the same pixel is outputted twice, effectively doing pixel/column doubling). However on the top screen, it does scanline doubling instead.  &lt;br /&gt;
Most likely the top screen receives two pixels at once per clock unit, outputting two scanlines simultaneously.&lt;br /&gt;
&lt;br /&gt;
On a 2DS, it seems to have no effect on the top part of the display, and on the bottom screen it just shifts the framebuffer to the right two pixels.&lt;br /&gt;
&lt;br /&gt;
GSP module only allows the LCD stereoscopy (3D) to be enabled when bit5=1 and bit6=0 here. When GSP module updates this register, GSP module will automatically disable the stereoscopy if those bits are not set for enabling stereoscopy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When both interlacing and alternative mode is disabled (bit6=0), the full resolution of the top screen (240x800) can be utilized if the PDC registers are updated to accomodate this higher resolution. GSP contains tables for this mode (gsp mode == 1). GSP automatically applies this mode if both bit5 and bit6 are cleared. This is also the default, and the only valid mode for the bottom screen in userland.&lt;br /&gt;
&lt;br /&gt;
If only AB interlacing is enabled (bit5=1, bit6=0), gsp detects this as a request to switch to 3D mode (gsp mode == 2), and enables the parallax barrier.  &lt;br /&gt;
It&#039;s unknown how to control this, but some other PDC registers control if interlacing should be done by true interleaving (both framebuffers are treated as 240x400), or by skipping lines (both framebuffers are treated as 240x800).&lt;br /&gt;
&lt;br /&gt;
If only alternative mode is enabled (bit5=0, bit6=1), gsp detects it as a request to switch back to 2D mode for the top screen (gsp mode == 0). This is also the default mode for the top screen.&lt;br /&gt;
&lt;br /&gt;
Both interlacing and scan doubling can&#039;t be enabled in usermode, but it works as expected in baremetal.&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer color formats ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Value&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| GL_RGBA8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| GL_RGB8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| GL_RGB565_OES&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| GL_RGB5_A1_OES&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| GL_RGBA4_OES&lt;br /&gt;
|}&lt;br /&gt;
Color components are laid out in reverse byte order, with the most significant bits used first (i.e. non-24-bit pixels are stored as a little-endian values). For instance, a raw data stream of two GL_RGB565_OES pixels looks like GGGBBBBB RRRRRGGG GGGBBBBB RRRRRGGG.&lt;br /&gt;
&lt;br /&gt;
Color formats 5, 6, and 7 are blocked by gsp, but they behave as pixel-doubled RGBA8 (not line doubling, but instead the same pixel is output twice) if used outside of userland.&lt;br /&gt;
&lt;br /&gt;
== Transfer Engine ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| Input physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C04&lt;br /&gt;
| Output physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C08&lt;br /&gt;
| DisplayTransfer output width (bits 0-15) and height (bits 16-31)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C0C&lt;br /&gt;
| DisplayTransfer input width and height&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C10&lt;br /&gt;
| Transfer flags&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C14&lt;br /&gt;
| ?, GSP writes value 0 here prior to writing to 0x1EF00C18 for DisplayTransfer&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C18&lt;br /&gt;
|  Setting bit0 starts the transfer; upon completion, bit0 is unset and bit8 is set&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C1C&lt;br /&gt;
|  ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C20&lt;br /&gt;
| TextureCopy total amount of data to copy, in bytes&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C24&lt;br /&gt;
| TextureCopy input line width (bits 0-15) and gap (bits 16-31), in 16 byte units&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C28&lt;br /&gt;
| TextureCopy output line width and gap&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Transfer flags:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| When set, the framebuffer data is flipped vertically&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Linear-&amp;gt;tiled mode (overrides tiled-&amp;gt;linear mode)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| This bit is required when the output width is less than the input width for the hardware to properly crop the lines, otherwise the output will be mis-aligned&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| TextureCopy mode (overrides all other modes)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Tiled-&amp;gt;tiled mode (overrides tiled-&amp;gt;linear, linear-&amp;gt;tiled modes)&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 10-8&lt;br /&gt;
| Input [[GPU/External_Registers#Framebuffer_color_formats|color format]]&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 14-12&lt;br /&gt;
| Output color format&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| Use 32x32 block tiling mode, instead of the usual 8x8 one (output dimensions must be multiples of 32, even if cropping with bit 2 set above)&lt;br /&gt;
|-&lt;br /&gt;
| 17-23&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 24-25&lt;br /&gt;
| Scale down the input image using a box filter (0 = No downscale, 1 = 2x1 downscale, 2 = 2x2 downscale, 3 = invalid)&lt;br /&gt;
|-&lt;br /&gt;
| 31-26&lt;br /&gt;
| Not writable&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP_Shared_Memory#Commands|GSP]] for DisplayTransfer and TextureCopy. TextureCopy registers are only used in TextureCopy mode; likewise, DisplayTransfer registers are only used when TextureCopy mode is not set. By default, DisplayTransfer will work in tiled-&amp;gt;linear mode.&lt;br /&gt;
&lt;br /&gt;
=== Tiled to linear ===&lt;br /&gt;
&lt;br /&gt;
Unswizzles the input buffer, this is usually used for transferring GPU framebuffer data onto LCD framebuffers. The following constraints apply:&lt;br /&gt;
&lt;br /&gt;
* Output dimensions must not be bigger than input ones.&lt;br /&gt;
* Width dimensions must be &amp;gt;= 64.&lt;br /&gt;
* Height dimensions must be &amp;gt;= 16.&lt;br /&gt;
* Width dimensions are required to be aligned to 16 bytes when doing RGB8 transfers.&lt;br /&gt;
** Otherwise they are required to be aligned to 8 bytes.&lt;br /&gt;
* If downscale is used, input and output dimensions should be the same, and width/2 must also follow alignment constraints.&lt;br /&gt;
&lt;br /&gt;
Format conversion results:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Conversion&lt;br /&gt;
!  Result&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGBA8 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: lightgreen&amp;quot; | Has interrupt, correct output&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB8 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, output not tested&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, output not tested&lt;br /&gt;
|-&lt;br /&gt;
| RGB565 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, output not tested&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, output not tested&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, output not tested&lt;br /&gt;
|-&lt;br /&gt;
| RGB5A1 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, output not tested&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGBA8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB8&lt;br /&gt;
| style=&amp;quot;background: salmon&amp;quot; | No interrupt&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB565&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, output not tested&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGB5A1&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, output not tested&lt;br /&gt;
|-&lt;br /&gt;
| RGBA4 -&amp;gt; RGBA4&lt;br /&gt;
| style=&amp;quot;background: yellow&amp;quot; | Has interrupt, output not tested&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== TextureCopy ===&lt;br /&gt;
&lt;br /&gt;
When bit 3 of the control register is set, the hardware performs a TextureCopy-mode transfer: no format conversions are done, instead a raw data copy is performed from the source to the destination, with a configurable gap between lines. All bits of the control register are ignored, except for input/output dimensions, which are used for line width and gap, and bit 2, which must be set when gaps are used.&lt;br /&gt;
&lt;br /&gt;
The total amount of bytes to copy is specified in the size register, the hardware loops reading lines from the input and writing them to the output until this amount is copied. The gap specifies the number of bytes to skip after each line read (a gap of 0 results in a contiguous read). Gaps do not count towards the total size of the transfer.&lt;br /&gt;
&lt;br /&gt;
When setting line width and gap they must be divided by 2 (it can be thought as the calculation being done in bits, and the values being stripped of their lower 4 bits for the alignment). For example, if the left half of a 32x32 RGB8 texture is to be copied, the parameters will be:&lt;br /&gt;
 line width = (16 * 24) &amp;gt;&amp;gt; 4 = 24&lt;br /&gt;
 gap = line width&lt;br /&gt;
 size = 16 * 32 * 3 = 1536&lt;br /&gt;
&lt;br /&gt;
By correctly calculating the input and output gap sizes it is possible to use this functionality to copy arbitrary sub-rectangles between differently-sized framebuffers or textures, which is one of its main uses over a regular no-conversion DisplayTransfer. When copying tiled textures/framebuffers it&#039;s important to remember that the contents of a tile are laid out sequentially in memory, and so this should be taken into account when calculating the transfer parameters.&lt;br /&gt;
&lt;br /&gt;
Specifying invalid/junk values for the TextureCopy dimensions can result in the GPU hanging while attempting to process this TextureCopy. For instance, when in contiguous mode the size must be at least 16; when in gap mode, the size must be at least 192, and the line width must not be 0.&lt;br /&gt;
&lt;br /&gt;
== Command List ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| Buffer size in bytes &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E8&lt;br /&gt;
| Buffer physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018F0&lt;br /&gt;
| Setting bit0 to 1 enables processing GPU command execution. Upon completion, bit0 seems to be reset to 0.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These 3 registers are used by [[GSP_Shared_Memory|GX command]] 1. This is used for [[GPU/Internal_Registers|GPU commands]].&lt;br /&gt;
&lt;br /&gt;
== Framebuffers ==&lt;br /&gt;
These LCD framebuffers normally contain the last rendered frames from the GPU. The framebuffers are drawn from left-to-right, instead of top-to-bottom.(Thus the beginning of the framebuffer is drawn starting at the left side of the screen)&lt;br /&gt;
&lt;br /&gt;
Both of the 3D screen left/right framebuffers are displayed regardless of the 3D slider&#039;s state, however when the 3D slider is set to &amp;quot;off&amp;quot; the 3D effect is disabled. Normally when the 3D slider&#039;s state is set to &amp;quot;off&amp;quot; the left/right framebuffer addresses are set to the same physical address. When the 3D effect is disabled and the left/right framebuffers are set to separate addresses, the LCD seems to alternate between displaying the left/right framebuffer each frame.&lt;br /&gt;
&lt;br /&gt;
==== Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00400 = 0x1C2&lt;br /&gt;
* 0x1EF00404 = 0xD1&lt;br /&gt;
* 0x1EF00408 = 0x1C1&lt;br /&gt;
* 0x1EF0040C = 0x1C1&lt;br /&gt;
* 0x1EF00410 = 0&lt;br /&gt;
* 0x1EF00414 = 0xCF&lt;br /&gt;
* 0x1EF00418 = 0xD1&lt;br /&gt;
* 0x1EF0041C = 0x1C501C1&lt;br /&gt;
* 0x1EF00420 = 0x10000&lt;br /&gt;
* 0x1EF00424 = 0x19D&lt;br /&gt;
* 0x1EF00428 = 2&lt;br /&gt;
* 0x1EF0042C = 0x1C2&lt;br /&gt;
* 0x1EF00430 = 0x1C2&lt;br /&gt;
* 0x1EF00434 = 0x1C2&lt;br /&gt;
* 0x1EF00438 = 1&lt;br /&gt;
* 0x1EF0043C = 2&lt;br /&gt;
* 0x1EF00440 = 0x1960192&lt;br /&gt;
* 0x1EF00444 = 0&lt;br /&gt;
* 0x1EF00448 = 0&lt;br /&gt;
* 0x1EF0045C = 0x19000F0&lt;br /&gt;
* 0x1EF00460 = 0x1c100d1&lt;br /&gt;
* 0x1EF00464 = 0x1920002&lt;br /&gt;
* 0x1EF00470 = 0x80340&lt;br /&gt;
* 0x1EF0049C = 0&lt;br /&gt;
&lt;br /&gt;
==== More Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00468 = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF0046C = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF00494 = 0x18300000&lt;br /&gt;
* 0x1EF00498 = 0x18300000&lt;br /&gt;
* 0x1EF00478 = 1, doesn&#039;t stay 1, read as 0&lt;br /&gt;
* 0x1EF00474 = 0x10501&lt;br /&gt;
&lt;br /&gt;
[[Category:GPU]]&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23532</id>
		<title>GPU/External Registers</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23532"/>
		<updated>2025-06-01T16:45:13Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the address range accessible from the ARM11, used to configure the basic GPU functionality. For information about the internal registers used for 3D rendering, see [[GPU/Internal Registers]].&lt;br /&gt;
&lt;br /&gt;
== Map ==&lt;br /&gt;
Address mappings for the external registers. GSPGPU:WriteHWRegs takes these addresses relative to 0x1EB00000. &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! User VA&lt;br /&gt;
! PA&lt;br /&gt;
! Length&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00000&lt;br /&gt;
| 0x10400000&lt;br /&gt;
| 4&lt;br /&gt;
| Hardware ID&lt;br /&gt;
| Bit2: new model&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00004&lt;br /&gt;
| 0x10400004&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00010&lt;br /&gt;
| 0x10400010&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill1]] &amp;quot;PSC0&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00020&lt;br /&gt;
| 0x10400020&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill2]] &amp;quot;PSC1&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00030&lt;br /&gt;
| 0x10400030&lt;br /&gt;
| 4&lt;br /&gt;
| VRAM bank control&lt;br /&gt;
| Bits 8-11 = bank[i] disabled; other bits are unused.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00034&lt;br /&gt;
| 0x10400034&lt;br /&gt;
| 4&lt;br /&gt;
| GPU Busy&lt;br /&gt;
| Bit26 = PSC0, bit27 = PSC1, Bit30 = PPF, Bit31 = P3D&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00050&lt;br /&gt;
| 0x10400050&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x22221200 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00054&lt;br /&gt;
| 0x10400054&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFF2 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000C0&lt;br /&gt;
| 0x104000C0&lt;br /&gt;
| 4&lt;br /&gt;
| Backlight control&lt;br /&gt;
| Writes 0x0 to allow backlights to turn off, 0x20000000 to force them always on.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00400&lt;br /&gt;
| 0x10400400&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC0&amp;quot; (top screen)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00500&lt;br /&gt;
| 0x10400500&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC1&amp;quot; (bottom)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| 0x10400C00&lt;br /&gt;
| ?&lt;br /&gt;
| [[#Transfer_Engine|Transfer Engine]] &amp;quot;DMA&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|colspan=&amp;quot;5&amp;quot;| 0x1EF01000/0x10401000 - 0x1EF01C00/0x10401C00 maps to [[GPU/Internal_Registers|GPU internal registers]]. These registers are usually not read/written directly here, but are written using the command list interface below (corresponding to the GPUREG_CMDBUF_* internal registers)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01000&lt;br /&gt;
| 0x10401000&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0 on GPU init and before the Command List is used&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01080&lt;br /&gt;
| 0x10401080&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x12345678 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010C0&lt;br /&gt;
| 0x104010C0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFFFFFFF0 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010D0&lt;br /&gt;
| 0x104010D0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 1 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF014??&lt;br /&gt;
| 0x104014??&lt;br /&gt;
| 0x14&lt;br /&gt;
| &amp;quot;PPF&amp;quot; ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| 0x104018E0&lt;br /&gt;
| 0x14&lt;br /&gt;
| [[#Command_List|Command List]] &amp;quot;P3D&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Memory Fill ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  User VA&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X0&lt;br /&gt;
| Buffer start physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X4&lt;br /&gt;
| Buffer end physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X8&lt;br /&gt;
| Fill value&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000XC&lt;br /&gt;
| Control. bit0: start/busy, bit1: finished, bit8-9: fill-width (0=16bit, 1=3=24bit, 2=32bit)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Memory fills are used to initialize buffers in memory with a given value, similar to memset. A memory fill is triggered by setting bit0 in the control register. Doing so aborts any running memory fills on that filling unit. Upon completion, the hardware unsets bit0 and sets bit1 and fires interrupt PSC0.&lt;br /&gt;
&lt;br /&gt;
The addresses must be part of VRAM.&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP Shared Memory#GX SetMemoryFill|GX SetMemoryFill]].&lt;br /&gt;
&lt;br /&gt;
== LCD Source Framebuffer Setup ==&lt;br /&gt;
&lt;br /&gt;
All of these registers must be accessed with 32bit operations regardless of the registers&#039; actual bit size.&lt;br /&gt;
&lt;br /&gt;
The naming of these parameters reflects the physical characteristics of the displays, and not the way the 3DS is normally held.&lt;br /&gt;
&lt;br /&gt;
To make sense of these values, the 3DS must be held in a way, so that the bottom screen is in the left hand, and the top screen is in the right hand, and that way the first pixel will be in the top-left corner, as it should be. If the 3DS is held normally, the first pixel is in the bottom-left corner.&lt;br /&gt;
&lt;br /&gt;
All pixel and scanline timing values are 12bits, unless noted. This also applies to those fields where two u16 are combined into one register. Each u16 field is only 12bits in size. timin&lt;br /&gt;
&lt;br /&gt;
The horizontal timing parameter order is as follows (values may overflow through HTotal register value):&lt;br /&gt;
 0x10 &amp;lt; 0x14 &amp;lt;= 0x60.LO &amp;lt;= 0x04 &amp;lt;= 0x60.HI &amp;lt;= 0x08 &amp;lt;= 0x0C &amp;lt;= 0x10&lt;br /&gt;
 0x18 &amp;lt;= 0x60.LO&lt;br /&gt;
&lt;br /&gt;
Timing starts from HCount == 0, then each absolute value in the beforementioned register chain triggers when HCount == register, latching the primitive display controller into a new mode.&lt;br /&gt;
There is an inherent latch order, where if two simultenaous events occur, one event wins over another.&lt;br /&gt;
&lt;br /&gt;
 Known latched modes (in order):&lt;br /&gt;
 - HSync (triggers a line to the LCD to move to the next line)&lt;br /&gt;
 - Back porch (area between HSync and border being displayed, no pixels pushed, min 16 pixel clocks, otherwise the screen gets glitchy)&lt;br /&gt;
 - Left border start (no image data is being displayed, just a configurable solid color)&lt;br /&gt;
 - Image start (pixel data is being DMA&#039;d from video memory or main RAM)&lt;br /&gt;
 - Right border start/Image end (border color is being displayed after the main image)&lt;br /&gt;
 - Unknown synchronization (supposed to be probably right border end, but this mode seems to be broken or not do anything)&lt;br /&gt;
 - Front porch (no pixels pushed, 68 clock min, otherwise the screen doesn&#039;t sync properly, and really glitches out)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Offset&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| HTotal&lt;br /&gt;
| The total width of a timing scanline. In other words, this is the horizontal refresh clock divider value.&lt;br /&gt;
&lt;br /&gt;
HClock = PClock / (HTotal + 1)&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| HStart&lt;br /&gt;
| Determines when the image is going to be displayed in the visible region (register 0x60).&lt;br /&gt;
|-&lt;br /&gt;
| 0x08&lt;br /&gt;
| HBR&lt;br /&gt;
| Right border start(?). Does nothing.&lt;br /&gt;
&lt;br /&gt;
While this register seems to have no impact on the image whatsoever, it still has to be set to a valid value.&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| 0x0C&lt;br /&gt;
| HPF&lt;br /&gt;
| Front porch. The image is blanked during this period, and no pixels are pushed to the LCD.&lt;br /&gt;
&lt;br /&gt;
Unknown why, but a single dot of red is displayed before entering this mode.&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| HSync&lt;br /&gt;
| Triggers a HSync pulse.&lt;br /&gt;
&lt;br /&gt;
Based on behavior, this needs to last at least a pixel clock for the LCD to register the sync.&lt;br /&gt;
|-&lt;br /&gt;
| 0x14&lt;br /&gt;
| HPB&lt;br /&gt;
| Back porch? Has to be at least one bigger than HSync, otherwise HSync never triggers.&lt;br /&gt;
&lt;br /&gt;
The display is blank, and the LCD displays nothing in this period (doesn&#039;t push pixels).&lt;br /&gt;
|-&lt;br /&gt;
| 0x18&lt;br /&gt;
| HBL&lt;br /&gt;
| Left border trigger treshold. Enables pushing pixels to the display.&lt;br /&gt;
&lt;br /&gt;
If this value is smaller than the back porch, then the back porch period will be zero, and the border will be immediately displayed upon entering the back porch period.&lt;br /&gt;
&lt;br /&gt;
Can be lower than HSync, as the back porch is what takes the controller out of HSync.&lt;br /&gt;
&lt;br /&gt;
Must be &amp;lt;= HDisp start (reg 0x60 low u16), otherwise no pixels will be pushed due to a glitched state.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1C&lt;br /&gt;
| H Interrupt timing&lt;br /&gt;
| Made up from two u16 values, PDC interrupt line is asserted when HCount == low u16, and most likely deasserted when HCount == high u16.&lt;br /&gt;
&lt;br /&gt;
There seems to be some limitations though:&lt;br /&gt;
* low u16 must be smaller than high u16&lt;br /&gt;
* if low u16 is less than HTotal then high u16 must also be smaller than HTotal&lt;br /&gt;
* setting low u16 to &amp;gt;= HTotal disables the interrupt ever firing&lt;br /&gt;
&lt;br /&gt;
This is configured by gsp in a way so that low u16 equals to HTotal, meaning the HSync interrupt will never fire.&lt;br /&gt;
|-&lt;br /&gt;
| 0x20&lt;br /&gt;
| low u16: ???&lt;br /&gt;
high u16: ???&lt;br /&gt;
| ???&lt;br /&gt;
|-&lt;br /&gt;
| 0x24&lt;br /&gt;
| VTotal&lt;br /&gt;
| Total height of the timing window. Can be interpreted as the vertical clock divider.&lt;br /&gt;
&lt;br /&gt;
VClock = PClock / (HTotal + 1) / (VTotal + 1)&lt;br /&gt;
&lt;br /&gt;
Setting this to 494 lowers framerate to about 50.040660858 Hz ((268111856 / 24) / (450 + 1) / (494 + 1)).&lt;br /&gt;
|-&lt;br /&gt;
| 0x28&lt;br /&gt;
| ?&lt;br /&gt;
| Seems to determine the vertical blanking interval.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Setting this to lower than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will cut off the top &amp;lt;code&amp;gt;VTotal - VDisp - thisvalue&amp;lt;/code&amp;gt; lines.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will make the image be pushed downwards with the overscan color visible.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;HTotal&amp;lt;/code&amp;gt; will make the GPU skip vertical pixel data synchronization (hence filling the screen with the rest of the pixel data past the given screen framebuffer size). Also will skip &amp;lt;code&amp;gt;thisvalue + somevalue - HTotal&amp;lt;/code&amp;gt; lines into the &amp;quot;global&amp;quot; pixel buffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x30&lt;br /&gt;
| ?&lt;br /&gt;
| Total amount of vertical scanlines in the pixel buffer, must be bigger than *an unknown blanking-like value*. If this value is less than VDisp then the last two scanlines will be repeated interlaced until VDisp is reached.&lt;br /&gt;
|-&lt;br /&gt;
| 0x34&lt;br /&gt;
| VDisp(?)&lt;br /&gt;
| Total amonut of vertical scanlines displayed (only for top screen it seems like). If this value is less than VTotal then the rest of the scanlines will not be updated on the screen, so those will slowly fade out. Must be bigger than *an unknown blanking-like value*, otherwise an underflow will happen.&lt;br /&gt;
|-&lt;br /&gt;
| 0x38&lt;br /&gt;
| Vertical data offset(?)&lt;br /&gt;
| ??? Seems to offset the screen upwards if this value is high enough. If this value is higher or equal to *some value* (aka. if less than one scanline is displayed on the screen) then the screen will lose synchronization.&lt;br /&gt;
|-&lt;br /&gt;
| 0x40&lt;br /&gt;
| V Interrupt timing&lt;br /&gt;
| Similar to H Interrupt timing (0x1C), except the comparison is done against VCount, the limitations are emposed on VTotal, and the interrupt that fires is VSync.&lt;br /&gt;
&lt;br /&gt;
One important note is that it seems like the VSync interrupt always fires at HCount == 0, and there doesn&#039;t seem to be a register to control this behavior.&lt;br /&gt;
|-&lt;br /&gt;
| 0x44&lt;br /&gt;
| ???&lt;br /&gt;
| similar functionality to 0x10&lt;br /&gt;
|-&lt;br /&gt;
| 0x48&lt;br /&gt;
| ???&lt;br /&gt;
| bit0 seems to disable HSync, bit8 seems to disable VSync, rest of the bits aren&#039;t writable.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4C&lt;br /&gt;
| Overscan filler color&lt;br /&gt;
| 24bits(? top 8bits ignored)&lt;br /&gt;
&lt;br /&gt;
When the visible region is being drawn, but the timing parameters are set up in a way that the framebuffer is smaller than the visible region, it will be filled by this color.&lt;br /&gt;
|-&lt;br /&gt;
| 0x50&lt;br /&gt;
| HCount&lt;br /&gt;
| Horizontal &amp;quot;beam position&amp;quot; counter. Note that this value does not equal to the current pixel being drawn.&lt;br /&gt;
|-&lt;br /&gt;
| 0x54&lt;br /&gt;
| VCount&lt;br /&gt;
| Vertical &amp;quot;beam position&amp;quot; counter. Note that the scanline being drawn isn&#039;t equal to this value.&lt;br /&gt;
|-&lt;br /&gt;
| 0x5C&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: Image width (including some offset?)&lt;br /&gt;
high u16: Image height??? (seems to be unused)&lt;br /&gt;
|-&lt;br /&gt;
| 0x60&lt;br /&gt;
| HDisp&lt;br /&gt;
| low u16: Image start (border --&amp;gt; pixel data)&lt;br /&gt;
high u16: Image end (pixel data --&amp;gt; border)&lt;br /&gt;
|-&lt;br /&gt;
| 0x64&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: unknown&lt;br /&gt;
high u16: framebuffer total height (amount of scanlines blitted regardless of framebuffer height)&lt;br /&gt;
|-&lt;br /&gt;
| 0x68&lt;br /&gt;
| Framebuffer A first address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x6C&lt;br /&gt;
| Framebuffer A second address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x70&lt;br /&gt;
| Framebuffer format and other settings&lt;br /&gt;
| See [[#Framebuffer_format|framebuffer format]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x74&lt;br /&gt;
| PDC control&lt;br /&gt;
| Bit 0: Enable display controller.&lt;br /&gt;
Bit 8: HBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 9: VBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 10: Error IRQ mask? (0 = enabled).&lt;br /&gt;
Bit 16: Output enable?&lt;br /&gt;
|-&lt;br /&gt;
| 0x78&lt;br /&gt;
| Framebuffer select and status&lt;br /&gt;
| Bit 0: Next framebuffer to display (after VBlank).&lt;br /&gt;
Bit 4: Currently displaying framebuffer?&lt;br /&gt;
Bit 8: Reset FIFO?&lt;br /&gt;
Bit 16: HBlank IRQ status/ack. Write 1 to aknowledge.&lt;br /&gt;
Bit 17: VBlank IRQ status/ack.&lt;br /&gt;
Bit 18: Error IRQ status/ack?&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| Color lookup table index select&lt;br /&gt;
| 8bits, write-only&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| Color lookup table indexed element&lt;br /&gt;
| Contains the value of the color lookup table indexed by the above register, 24bits, RGB8 (0x00BBGGRR)  &lt;br /&gt;
Accessing this register will increase the index register by one&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| Framebuffer stride&lt;br /&gt;
| 32bits (bottom 3bits ignored?)&lt;br /&gt;
&lt;br /&gt;
Distance in bytes between the start of two framebuffer rows (must be a multiple of 8).&lt;br /&gt;
&lt;br /&gt;
In other words, this can be interpreted as the amount to add to the framebuffer pointer after displaying a scanline.&lt;br /&gt;
&lt;br /&gt;
Setting this to zero will cause only the first line of the image to be displayed repeated on the entire display. With the HSync interrupt it&#039;s possible to &amp;quot;race the beam&amp;quot; to (ab)use this feature.&lt;br /&gt;
&lt;br /&gt;
Because of this simplicity, writing a negative value here VFlips the image, although that requires the framebuffer pointer register to be set to the start of the last scanline, instead of at the start of the framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| Framebuffer B first address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| Framebuffer B second address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer format ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 2-0&lt;br /&gt;
| [[#Framebuffer_color_formats|Color format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5-4&lt;br /&gt;
| Framebuffer interlacing mode&lt;br /&gt;
&lt;br /&gt;
 0 - A  (no interlacing)&lt;br /&gt;
 1 - AA (scanline doubling)&lt;br /&gt;
 2 - AB (interlace enable)&lt;br /&gt;
 3 - BA (same as above, but the fields are inverted)&lt;br /&gt;
&lt;br /&gt;
In AB and BA interlace modes, a scanline from each framebuffer is output in an alternating manner. In AB mode, Framebuffer A is output on the frist display scanline. Similarly, in BA mode, Framebuffer B gets output to the first display scanline.&lt;br /&gt;
&lt;br /&gt;
The way AB and BA modes work, is that a scanline is output, the framebuffer stride value is added to the internal scanline pointer value, and the other framebuffer is selected. And this alternates until the end of the draw region.&lt;br /&gt;
&lt;br /&gt;
AA interlacing works like AB interlacing, except both internal framebuffer pointers are set to the Framebuffer A pointer value.&lt;br /&gt;
&lt;br /&gt;
In A mode (no interlacing), it doesn&#039;t switch to the other framebuffer at the end of outpuitting a scanline to the display.&lt;br /&gt;
&lt;br /&gt;
Bottom screen has this set to 0 (A mode, no interlacing) at all times.  &lt;br /&gt;
Top screen uses AB interlacing in 3D mode (with 3D slider enabled), and A mode (no interlacing) in 2D mode.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Alternative pixel output mode*&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 9-8&lt;br /&gt;
| DMA size&lt;br /&gt;
&lt;br /&gt;
 0 -  4 FCRAM words (32 bytes)&lt;br /&gt;
 1 -  8 FCRAM words (64 bytes)&lt;br /&gt;
 2 - 16 FCRAM words (128 bytes)&lt;br /&gt;
 3 - ???&lt;br /&gt;
&lt;br /&gt;
FCRAM doesn&#039;t support DMA size 3, as it can only burst up to 16 words (128 bytes), and will show a black screen instead.&lt;br /&gt;
|-&lt;br /&gt;
| 31-16&lt;br /&gt;
| Unknown&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt; The weird thing about bit6, is that it works different between the bottom and top LCD. On the bottom LCD, it doubles the number of outputted pixels (so the same pixel is outputted twice, effectively doing pixel/column doubling). However on the top screen, it does scanline doubling instead.  &lt;br /&gt;
Most likely the top screen receives two pixels at once per clock unit, outputting two scanlines simultaneously.&lt;br /&gt;
&lt;br /&gt;
On a 2DS, it seems to have no effect on the top part of the display, and on the bottom screen it just shifts the framebuffer to the right two pixels.&lt;br /&gt;
&lt;br /&gt;
GSP module only allows the LCD stereoscopy (3D) to be enabled when bit5=1 and bit6=0 here. When GSP module updates this register, GSP module will automatically disable the stereoscopy if those bits are not set for enabling stereoscopy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When both interlacing and alternative mode is disabled (bit6=0), the full resolution of the top screen (240x800) can be utilized if the PDC registers are updated to accomodate this higher resolution. GSP contains tables for this mode (gsp mode == 1). GSP automatically applies this mode if both bit5 and bit6 are cleared. This is also the default, and the only valid mode for the bottom screen in userland.&lt;br /&gt;
&lt;br /&gt;
If only AB interlacing is enabled (bit5=1, bit6=0), gsp detects this as a request to switch to 3D mode (gsp mode == 2), and enables the parallax barrier.  &lt;br /&gt;
It&#039;s unknown how to control this, but some other PDC registers control if interlacing should be done by true interleaving (both framebuffers are treated as 240x400), or by skipping lines (both framebuffers are treated as 240x800).&lt;br /&gt;
&lt;br /&gt;
If only alternative mode is enabled (bit5=0, bit6=1), gsp detects it as a request to switch back to 2D mode for the top screen (gsp mode == 0). This is also the default mode for the top screen.&lt;br /&gt;
&lt;br /&gt;
Both interlacing and scan doubling can&#039;t be enabled in usermode, but it works as expected in baremetal.&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer color formats ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Value&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| GL_RGBA8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| GL_RGB8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| GL_RGB565_OES&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| GL_RGB5_A1_OES&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| GL_RGBA4_OES&lt;br /&gt;
|}&lt;br /&gt;
Color components are laid out in reverse byte order, with the most significant bits used first (i.e. non-24-bit pixels are stored as a little-endian values). For instance, a raw data stream of two GL_RGB565_OES pixels looks like GGGBBBBB RRRRRGGG GGGBBBBB RRRRRGGG.&lt;br /&gt;
&lt;br /&gt;
Color formats 5, 6, and 7 are blocked by gsp, but they behave as pixel-doubled RGBA8 (not line doubling, but instead the same pixel is output twice) if used outside of userland.&lt;br /&gt;
&lt;br /&gt;
== Transfer Engine ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| Input physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C04&lt;br /&gt;
| Output physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C08&lt;br /&gt;
| DisplayTransfer output width (bits 0-15) and height (bits 16-31).&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C0C&lt;br /&gt;
| DisplayTransfer input width and height.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C10&lt;br /&gt;
| Transfer flags. (See below)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C14&lt;br /&gt;
| GSP module writes value 0 here prior to writing to 0x1EF00C18, for cmd3.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C18&lt;br /&gt;
|  Setting bit0 starts the transfer. Upon completion, bit0 is unset and bit8 is set.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C1C&lt;br /&gt;
|  ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C20&lt;br /&gt;
| TextureCopy total amount of data to copy, in bytes.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C24&lt;br /&gt;
| TextureCopy input line width (bits 0-15) and gap (bits 16-31), in 16 byte units.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C28&lt;br /&gt;
| TextureCopy output line width and gap.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP_Shared_Memory|GX command]] 3 and 4. For cmd4, *0x1EF00C18 |= 1 is used instead of just writing value 1. The DisplayTransfer registers are only used if bit 3 of the flags is unset and ignored otherwise. The TextureCopy registers are likewise only used if bit 3 is set, and ignored otherwise.&lt;br /&gt;
&lt;br /&gt;
The minimum supported dimension for output is 64x64, anything lower will hang the engine.&lt;br /&gt;
&lt;br /&gt;
==== Flags Register - 0x1EF00C10 ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| When set, the framebuffer data is flipped vertically.&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| When set, the input framebuffer is treated as linear and converted to tiled in the output, converts tiled-&amp;gt;linear when unset.&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| This bit is required when the output width is less than the input width for the hardware to properly crop the lines, otherwise the output will be mis-aligned.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Uses a TextureCopy mode transfer. See below for details.&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Don&#039;t perform tiled-linear conversion. Incompatible with bit 1, so only tiled-tiled transfers can be done, not linear-linear.&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 10-8&lt;br /&gt;
| Input framebuffer color format, value0 and value1 are the same as the [[GPU Registers#Framebuffer_color_formats|LCD Source Framebuffer Formats]] (usually zero)&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 14-12&lt;br /&gt;
| Output framebuffer color format&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| Use 32x32 block tiling mode, instead of the usual 8x8 one. Output dimensions must be multiples of 32, even if cropping with bit 2 set above.&lt;br /&gt;
|-&lt;br /&gt;
| 17-23&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 24-25&lt;br /&gt;
| Scale down the input image using a box filter. 0 = No downscale, 1 = 2x1 downscale. 2 = 2x2 downscale, 3 = invalid&lt;br /&gt;
|-&lt;br /&gt;
| 31-26&lt;br /&gt;
| Not writable&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== TextureCopy ===&lt;br /&gt;
&lt;br /&gt;
When bit 3 of the control register is set, the hardware performs a TextureCopy-mode transfer: no format conversions are done, instead a raw data copy is performed from the source to the destination, with a configurable gap between lines. All bits of the control register are ignored, except for input/output dimensions, which are used for line width and gap, and bit 2, which must be set when gaps are used.&lt;br /&gt;
&lt;br /&gt;
The total amount of bytes to copy is specified in the size register, the hardware loops reading lines from the input and writing them to the output until this amount is copied. The gap specifies the number of bytes to skip after each line read (a gap of 0 results in a contiguous read). Gaps do not count towards the total size of the transfer.&lt;br /&gt;
&lt;br /&gt;
When setting line width and gap they must be divided by 2 (it can be thought as the calculation being done in bits, and the values being stripped of their lower 4 bits for the alignment). For example, if the left half of a 32x32 RGB8 texture is to be copied, the parameters will be:&lt;br /&gt;
 line width = (16 * 24) &amp;gt;&amp;gt; 4 = 24&lt;br /&gt;
 gap = line width&lt;br /&gt;
 size = 16 * 32 * 3 = 1536&lt;br /&gt;
&lt;br /&gt;
By correctly calculating the input and output gap sizes it is possible to use this functionality to copy arbitrary sub-rectangles between differently-sized framebuffers or textures, which is one of its main uses over a regular no-conversion DisplayTransfer. When copying tiled textures/framebuffers it&#039;s important to remember that the contents of a tile are laid out sequentially in memory, and so this should be taken into account when calculating the transfer parameters.&lt;br /&gt;
&lt;br /&gt;
Specifying invalid/junk values for the TextureCopy dimensions can result in the GPU hanging while attempting to process this TextureCopy. For instance, when in contiguous mode the size must be at least 16; when in gap mode, the size must be at least 192, and the line width must not be 0.&lt;br /&gt;
&lt;br /&gt;
== Command List ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| Buffer size in bytes &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E8&lt;br /&gt;
| Buffer physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018F0&lt;br /&gt;
| Setting bit0 to 1 enables processing GPU command execution. Upon completion, bit0 seems to be reset to 0.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These 3 registers are used by [[GSP_Shared_Memory|GX command]] 1. This is used for [[GPU/Internal_Registers|GPU commands]].&lt;br /&gt;
&lt;br /&gt;
== Framebuffers ==&lt;br /&gt;
These LCD framebuffers normally contain the last rendered frames from the GPU. The framebuffers are drawn from left-to-right, instead of top-to-bottom.(Thus the beginning of the framebuffer is drawn starting at the left side of the screen)&lt;br /&gt;
&lt;br /&gt;
Both of the 3D screen left/right framebuffers are displayed regardless of the 3D slider&#039;s state, however when the 3D slider is set to &amp;quot;off&amp;quot; the 3D effect is disabled. Normally when the 3D slider&#039;s state is set to &amp;quot;off&amp;quot; the left/right framebuffer addresses are set to the same physical address. When the 3D effect is disabled and the left/right framebuffers are set to separate addresses, the LCD seems to alternate between displaying the left/right framebuffer each frame.&lt;br /&gt;
&lt;br /&gt;
==== Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00400 = 0x1C2&lt;br /&gt;
* 0x1EF00404 = 0xD1&lt;br /&gt;
* 0x1EF00408 = 0x1C1&lt;br /&gt;
* 0x1EF0040C = 0x1C1&lt;br /&gt;
* 0x1EF00410 = 0&lt;br /&gt;
* 0x1EF00414 = 0xCF&lt;br /&gt;
* 0x1EF00418 = 0xD1&lt;br /&gt;
* 0x1EF0041C = 0x1C501C1&lt;br /&gt;
* 0x1EF00420 = 0x10000&lt;br /&gt;
* 0x1EF00424 = 0x19D&lt;br /&gt;
* 0x1EF00428 = 2&lt;br /&gt;
* 0x1EF0042C = 0x1C2&lt;br /&gt;
* 0x1EF00430 = 0x1C2&lt;br /&gt;
* 0x1EF00434 = 0x1C2&lt;br /&gt;
* 0x1EF00438 = 1&lt;br /&gt;
* 0x1EF0043C = 2&lt;br /&gt;
* 0x1EF00440 = 0x1960192&lt;br /&gt;
* 0x1EF00444 = 0&lt;br /&gt;
* 0x1EF00448 = 0&lt;br /&gt;
* 0x1EF0045C = 0x19000F0&lt;br /&gt;
* 0x1EF00460 = 0x1c100d1&lt;br /&gt;
* 0x1EF00464 = 0x1920002&lt;br /&gt;
* 0x1EF00470 = 0x80340&lt;br /&gt;
* 0x1EF0049C = 0&lt;br /&gt;
&lt;br /&gt;
==== More Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00468 = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF0046C = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF00494 = 0x18300000&lt;br /&gt;
* 0x1EF00498 = 0x18300000&lt;br /&gt;
* 0x1EF00478 = 1, doesn&#039;t stay 1, read as 0&lt;br /&gt;
* 0x1EF00474 = 0x10501&lt;br /&gt;
&lt;br /&gt;
[[Category:GPU]]&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23530</id>
		<title>GSP Shared Memory</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23530"/>
		<updated>2025-05-29T21:10:46Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Use conventional names&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the structure of the GSP [[GSPGPU:RegisterInterruptRelayQueue|shared]] memory. Interrupt, framebuffer, and GX command data is stored here.&lt;br /&gt;
&lt;br /&gt;
=Interrupt Queue=&lt;br /&gt;
&lt;br /&gt;
The Interrupt queue is located at sharedMemBase + (clientID * 0x40).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| Offset from the count where to save incoming interrupts&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| Count (max 0x20 for PDC, 0x34 for others)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| Missed other interrupts (set to 1 when 0 and count &amp;gt;= 0x34)&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| Flags (bit0 = skip PDC)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4-0x7&lt;br /&gt;
| Missed PDC0 (incremented when flags.bit0 is clear and count &amp;gt;= 0x20) &lt;br /&gt;
|-&lt;br /&gt;
| 0x8-0xB&lt;br /&gt;
| Missed PDC1 (same as above)&lt;br /&gt;
|-&lt;br /&gt;
| 0xC-0x3F&lt;br /&gt;
| Interrupt list (u8) (0=PSC0, 1=PSC1, 2=PDC0/VBlankTop, 3=PDC1/VBlankBottom, 4=PPF, 5=P3D, 6=DMA)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP fills the interrupt list, then triggers the event set with [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]] for the specified process(es).&lt;br /&gt;
&lt;br /&gt;
PDC interrupts are sent to all processes; other interrupts are only sent to the process with rendering rights.&lt;br /&gt;
&lt;br /&gt;
When issuing a [[GSP_Shared_Memory#Trigger_Memory_Fill|Memory Fill]] command with both buffers set GSP will only dispatch PSC0.&lt;br /&gt;
&lt;br /&gt;
= Framebuffer Info =&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the top LCD is located at sharedMemBase + 0x200 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the bottom LCD is located at sharedMemBase + 0x240 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
|  Framebuffer info [[GSPGPU:SetBufferSwap|entry]] index&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Flags (bit0 = client has set new data)&lt;br /&gt;
|-&lt;br /&gt;
| 3-2&lt;br /&gt;
| Padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Structure ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Active framebuffer (0 = first, 1 = second)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Left framebuffer VA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Right framebuffer VA (top screen only)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Stride]] (offset 0x90)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| [[GPU/External_Registers#Framebuffer_format|Format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Status]] (offset 0x78)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ? (&amp;quot;Attribute&amp;quot;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a process sets this framebuffer info, it sets index to &amp;lt;nowiki&amp;gt;(index+1) &amp;amp; 1&amp;lt;/nowiki&amp;gt;. Then it writes the framebuffer info entry, and sets flag to value 1. The GSP module loads this framebuffer info entry data into GSP state once the [[GPU]] finishes processing GX commands 3 or 4. Once the GSP module finishes loading this framebuffer info, it sets flag to value 0, then it will not load the framebuffer info again until flag is value 1. After loading this entry data into GSP state, the GSP module then writes this framebuffer state to the [[LCD]] registers. GSP module automatically updates the LCD framebuffer registers each time GX commands 3 or 4 finish, even when this shared memory data was not updated by the application.(GSP module toggles the active framebuffer register when automatically updating LCD registers, when shared memory data is not used)&lt;br /&gt;
&lt;br /&gt;
The two 0x1C-byte framebuffer info entries are located at framebufferinfo+4.&lt;br /&gt;
&lt;br /&gt;
= 3D Slider and 3D [[GSPGPU:SetLedForceOff|LED]] =&lt;br /&gt;
&lt;br /&gt;
See [[Configuration Memory]].&lt;br /&gt;
&lt;br /&gt;
= GX Command Queue =&lt;br /&gt;
&lt;br /&gt;
This command queue is located at sharedMemBase + 0x800 + (clientID * 0x200). It consists of an header followed by at most 15 command entries.&lt;br /&gt;
&lt;br /&gt;
The queue header has the following structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Index of the command to process, this is incremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Total commands to process, this is incremented by the application when adding the command to the queue, and decremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Status (0x1 = halted, 0x80 = error)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When bit0 is set, further processing of commands is halted until the client resets the flag and calls [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 7-4&lt;br /&gt;
| Result code for the last command which failed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
After adding a command, [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]] must be used to start command processing (official code does so when the total commands field is 1).&lt;br /&gt;
&lt;br /&gt;
== Commands ==&lt;br /&gt;
&lt;br /&gt;
A command entry is made of 8 words. The first word is the command header, subsequent words represent command specific parameters.&lt;br /&gt;
&lt;br /&gt;
The command header has the following structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| When bit0 is set, GSP stops processing further commands (can be used for packing together sets of commands)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When set, the command fails if GSP is busy handling any other command; otherwise, it only fails if GSP is busy handling a command of the same kind&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Addresses specified in command parameters are virtual addresses. Depending on the command, there might be constraints on the accepted parameters. In general, some commands require parameters to be aligned, and addresses are expected to be on [[Memory_Management#Memory_Mapping|linear]], [[Memory_layout#0x1F000000_.28New_3DS_only.29|QTM]] or VRAM memory.&lt;br /&gt;
&lt;br /&gt;
=== RequestDMA ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x00)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush source (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command issues a [[Corelink_DMA_Engines|DMA request]] as the process with [[GSPGPU:AcquireRight|rendering rights]]. When the destination address is within VRAM, GSP places itself as the destination process: this makes it possible to transfer data in VRAM without needing it listed in the destination process [[NCCH/Extended_Header#ARM11_Kernel_Capabilities|exheader mappings]]. Otherwise, both source and destination of the DMA request are the process with rendering rights.&lt;br /&gt;
&lt;br /&gt;
The source buffer must be mapped as readable in the source process, while the destination address must be mapped as writable in the destination process, otherwise GSP calls [[SVC|svcBreak]]. When flushing is enabled and the source address is above VRAM, svcFlushProcessDataCache is used to flush the source buffer.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the command does nothing.&lt;br /&gt;
&lt;br /&gt;
=== ProcessCommandList ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x01)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Update gas additive blend results (0 = don&#039;t update, 1 = update)&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush buffer (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Command_List|Command List registers]], and optionally updates gas additive blend results after command processing has ended.&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Address and size should be both aligned to 8 bytes, and the address should be in linear, QTM or VRAM memory, otherwise PA 0 is used. When flushing is enabled, [[SVC|svcFlushProcessDataCache]] is used to flush the buffer on the process that has acquired rendering rights.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the command does nothing.&lt;br /&gt;
&lt;br /&gt;
=== MemoryFill ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x02)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer 0 start address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer 0 value&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buffer 0 end address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buffer 1 start address&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buffer 1 value&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buffer 1 end address&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Control0 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (Control1 &amp;lt;&amp;lt; 16)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Memory_Fill|Memory Fill registers]].&lt;br /&gt;
&lt;br /&gt;
Addresses should be aligned to 8 bytes and must be in linear, QTM or VRAM memory, otherwise error 0xE0E02BF5 (GSP_INVALID_ADDRESS) is returned. The start address for a buffer must be below its end address, else the same error is returned. If the start address for a buffer is 0, that buffer is skipped; otherwise, its relative PSC unit is used for the fill operation.&lt;br /&gt;
&lt;br /&gt;
=== DisplayTransfer ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x03)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Source dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Output dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Flags&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Transfer_Engine|Display Transfer registers]].&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Addresses should be aligned to 8 bytes and should be in linear, QTM or VRAM memory, otherwise PA 0 is used.&lt;br /&gt;
&lt;br /&gt;
=== TextureCopy ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x04)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Line width &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (gap &amp;lt;&amp;lt; 16)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Same as above, for the destination&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Flags&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#TextureCopy|Texture Copy registers]]. Note that GSP doesn&#039;t enforce bit3 of the flags to be set.&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Addresses and size should be aligned to 8 bytes, and the addresses should be in linear, QTM or VRAM memory, otherwise PA 0 is used.&lt;br /&gt;
&lt;br /&gt;
=== FlushCacheRegions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x05)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer 0 address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer 0 size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buffer 1 address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buffer 1 size&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buffer 2 address&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buffer 2 size&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command calls svcFlushProcessDataCache for each buffer on the process that has acquired rendering rights.&lt;br /&gt;
&lt;br /&gt;
If any call fails, its error is returned; If any buffer has size 0, the buffer is skipped. In both cases, subsequent buffers are not processed.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the error 0xD8202A06 (GSP_NO_RIGHT) is returned.&lt;br /&gt;
&lt;br /&gt;
== Bugs ==&lt;br /&gt;
&lt;br /&gt;
* When issuing a DMA request, GSP attempts to acquire an internal semaphore that rules CDMA access; this semaphore is never released on failure paths. While this is generally not an issue, as GSP breaks on DMA failures, it becomes a problem if the DMA request is done with cache flushing: in that case, GSP will error silently, causing a deadlock in DMA code.&lt;br /&gt;
* When handling GX commands apart from RequestDMA and ProcessCommandList, GSP sets the relative busy flags in internal state before executing the commands. This means that, if the relevant interrupts are never triggered (eg. on invalid parameters), the busy flags never get reset, preventing execution of future commands of the same kind.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23529</id>
		<title>GSP Shared Memory</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23529"/>
		<updated>2025-05-29T21:08:03Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Add bugs section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the structure of the GSP [[GSPGPU:RegisterInterruptRelayQueue|shared]] memory. Interrupt, framebuffer, and GX command data is stored here.&lt;br /&gt;
&lt;br /&gt;
=Interrupt Queue=&lt;br /&gt;
&lt;br /&gt;
The Interrupt queue is located at sharedMemBase + (clientID * 0x40).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| Offset from the count where to save incoming interrupts&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| Count (max 0x20 for PDC, 0x34 for others)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| Missed other interrupts (set to 1 when 0 and count &amp;gt;= 0x34)&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| Flags (bit0 = skip PDC)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4-0x7&lt;br /&gt;
| Missed PDC0 (incremented when flags.bit0 is clear and count &amp;gt;= 0x20) &lt;br /&gt;
|-&lt;br /&gt;
| 0x8-0xB&lt;br /&gt;
| Missed PDC1 (same as above)&lt;br /&gt;
|-&lt;br /&gt;
| 0xC-0x3F&lt;br /&gt;
| Interrupt list (u8) (0=PSC0, 1=PSC1, 2=PDC0/VBlankTop, 3=PDC1/VBlankBottom, 4=PPF, 5=P3D, 6=DMA)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP fills the interrupt list, then triggers the event set with [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]] for the specified process(es).&lt;br /&gt;
&lt;br /&gt;
PDC interrupts are sent to all processes; other interrupts are only sent to the process with rendering rights.&lt;br /&gt;
&lt;br /&gt;
When issuing a [[GSP_Shared_Memory#Trigger_Memory_Fill|Memory Fill]] command with both buffers set GSP will only dispatch PSC0.&lt;br /&gt;
&lt;br /&gt;
= Framebuffer Info =&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the top LCD is located at sharedMemBase + 0x200 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the bottom LCD is located at sharedMemBase + 0x240 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
|  Framebuffer info [[GSPGPU:SetBufferSwap|entry]] index&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Flags (bit0 = client has set new data)&lt;br /&gt;
|-&lt;br /&gt;
| 3-2&lt;br /&gt;
| Padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Structure ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Active framebuffer (0 = first, 1 = second)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Left framebuffer VA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Right framebuffer VA (top screen only)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Stride]] (offset 0x90)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| [[GPU/External_Registers#Framebuffer_format|Format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Status]] (offset 0x78)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ? (&amp;quot;Attribute&amp;quot;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a process sets this framebuffer info, it sets index to &amp;lt;nowiki&amp;gt;(index+1) &amp;amp; 1&amp;lt;/nowiki&amp;gt;. Then it writes the framebuffer info entry, and sets flag to value 1. The GSP module loads this framebuffer info entry data into GSP state once the [[GPU]] finishes processing GX commands 3 or 4. Once the GSP module finishes loading this framebuffer info, it sets flag to value 0, then it will not load the framebuffer info again until flag is value 1. After loading this entry data into GSP state, the GSP module then writes this framebuffer state to the [[LCD]] registers. GSP module automatically updates the LCD framebuffer registers each time GX commands 3 or 4 finish, even when this shared memory data was not updated by the application.(GSP module toggles the active framebuffer register when automatically updating LCD registers, when shared memory data is not used)&lt;br /&gt;
&lt;br /&gt;
The two 0x1C-byte framebuffer info entries are located at framebufferinfo+4.&lt;br /&gt;
&lt;br /&gt;
= 3D Slider and 3D [[GSPGPU:SetLedForceOff|LED]] =&lt;br /&gt;
&lt;br /&gt;
See [[Configuration Memory]].&lt;br /&gt;
&lt;br /&gt;
= GX Command Queue =&lt;br /&gt;
&lt;br /&gt;
This command queue is located at sharedMemBase + 0x800 + (clientID * 0x200). It consists of an header followed by at most 15 command entries.&lt;br /&gt;
&lt;br /&gt;
The queue header has the following structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Index of the command to process, this is incremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Total commands to process, this is incremented by the application when adding the command to the queue, and decremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Status (0x1 = halted, 0x80 = error)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When bit0 is set, further processing of commands is halted until the client resets the flag and calls [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 7-4&lt;br /&gt;
| Result code for the last command which failed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
After adding a command, [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]] must be used to start command processing (official code does so when the total commands field is 1).&lt;br /&gt;
&lt;br /&gt;
== Commands ==&lt;br /&gt;
&lt;br /&gt;
A command entry is made of 8 words. The first word is the command header, subsequent words represent command specific parameters.&lt;br /&gt;
&lt;br /&gt;
The command header has the following structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| When bit0 is set, GSP stops processing further commands (can be used for packing together sets of commands)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When set, the command fails if GSP is busy handling any other command; otherwise, it only fails if GSP is busy handling a command of the same kind&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Addresses specified in command parameters are virtual addresses. Depending on the command, there might be constraints on the accepted parameters. In general, some commands require parameters to be aligned, and addresses are expected to be on [[Memory_Management#Memory_Mapping|linear]], [[Memory_layout#0x1F000000_.28New_3DS_only.29|QTM]] or VRAM memory.&lt;br /&gt;
&lt;br /&gt;
=== Trigger DMA Request ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x00)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush source (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command issues a [[Corelink_DMA_Engines|DMA request]] as the process with [[GSPGPU:AcquireRight|rendering rights]]. When the destination address is within VRAM, GSP places itself as the destination process: this makes it possible to transfer data in VRAM without needing it listed in the destination process [[NCCH/Extended_Header#ARM11_Kernel_Capabilities|exheader mappings]]. Otherwise, both source and destination of the DMA request are the process with rendering rights.&lt;br /&gt;
&lt;br /&gt;
The source buffer must be mapped as readable in the source process, while the destination address must be mapped as writable in the destination process, otherwise GSP calls [[SVC|svcBreak]]. When flushing is enabled and the source address is above VRAM, svcFlushProcessDataCache is used to flush the source buffer.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the command does nothing.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Command List Processing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x01)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Update gas additive blend results (0 = don&#039;t update, 1 = update)&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush buffer (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Command_List|Command List registers]], and optionally updates gas additive blend results after command processing has ended.&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Address and size should be both aligned to 8 bytes, and the address should be in linear, QTM or VRAM memory, otherwise PA 0 is used. When flushing is enabled, [[SVC|svcFlushProcessDataCache]] is used to flush the buffer on the process that has acquired rendering rights.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the command does nothing.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Memory Fill ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x02)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer 0 start address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer 0 value&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buffer 0 end address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buffer 1 start address&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buffer 1 value&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buffer 1 end address&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Control0 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (Control1 &amp;lt;&amp;lt; 16)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Memory_Fill|Memory Fill registers]].&lt;br /&gt;
&lt;br /&gt;
Addresses should be aligned to 8 bytes and must be in linear, QTM or VRAM memory, otherwise error 0xE0E02BF5 (GSP_INVALID_ADDRESS) is returned. The start address for a buffer must be below its end address, else the same error is returned. If the start address for a buffer is 0, that buffer is skipped; otherwise, its relative PSC unit is used for the fill operation.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Display Transfer ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x03)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Source dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Output dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Flags&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Transfer_Engine|Display Transfer registers]].&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Addresses should be aligned to 8 bytes and should be in linear, QTM or VRAM memory, otherwise PA 0 is used.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Texture Copy ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x04)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Line width &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (gap &amp;lt;&amp;lt; 16)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Same as above, for the destination&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Flags&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#TextureCopy|Texture Copy registers]]. Note that GSP doesn&#039;t enforce bit3 of the flags to be set.&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Addresses and size should be aligned to 8 bytes, and the addresses should be in linear, QTM or VRAM memory, otherwise PA 0 is used.&lt;br /&gt;
&lt;br /&gt;
=== Flush Cache Regions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x05)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer 0 address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer 0 size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buffer 1 address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buffer 1 size&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buffer 2 address&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buffer 2 size&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command calls svcFlushProcessDataCache for each buffer on the process that has acquired rendering rights.&lt;br /&gt;
&lt;br /&gt;
If any call fails, its error is returned; If any buffer has size 0, the buffer is skipped. In both cases, subsequent buffers are not processed.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the error 0xD8202A06 (GSP_NO_RIGHT) is returned.&lt;br /&gt;
&lt;br /&gt;
== Bugs ==&lt;br /&gt;
&lt;br /&gt;
* When issuing a DMA request, GSP attempts to acquire an internal semaphore that rules CDMA access; this semaphore is never released on failure paths. While this is generally not an issue, as GSP breaks on DMA failures, it becomes a problem if the DMA request is done with cache flushing: in that case, GSP will error silently, causing a deadlock in DMA code.&lt;br /&gt;
* When handling GX commands apart from RequestDMA and ProcessCommandList, GSP sets the relative busy flags in internal state before executing the commands. This means that, if the relevant interrupts are never triggered (eg. on invalid parameters), the busy flags never get reset, preventing execution of future commands of the same kind.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23522</id>
		<title>GSP Shared Memory</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23522"/>
		<updated>2025-05-29T20:45:28Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: lil redesign&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the structure of the GSP [[GSPGPU:RegisterInterruptRelayQueue|shared]] memory. Interrupt, framebuffer, and GX command data is stored here.&lt;br /&gt;
&lt;br /&gt;
=Interrupt Queue=&lt;br /&gt;
&lt;br /&gt;
The Interrupt queue is located at sharedMemBase + (clientID * 0x40).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| Offset from the count where to save incoming interrupts&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| Count (max 0x20 for PDC, 0x34 for others)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| Missed other interrupts (set to 1 when 0 and count &amp;gt;= 0x34)&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| Flags (bit0 = skip PDC)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4-0x7&lt;br /&gt;
| Missed PDC0 (incremented when flags.bit0 is clear and count &amp;gt;= 0x20) &lt;br /&gt;
|-&lt;br /&gt;
| 0x8-0xB&lt;br /&gt;
| Missed PDC1 (same as above)&lt;br /&gt;
|-&lt;br /&gt;
| 0xC-0x3F&lt;br /&gt;
| Interrupt list (u8) (0=PSC0, 1=PSC1, 2=PDC0/VBlankTop, 3=PDC1/VBlankBottom, 4=PPF, 5=P3D, 6=DMA)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP fills the interrupt list, then triggers the event set with [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]] for the specified process(es).&lt;br /&gt;
&lt;br /&gt;
PDC interrupts are sent to all processes; other interrupts are only sent to the process with rendering rights.&lt;br /&gt;
&lt;br /&gt;
When issuing a [[GSP_Shared_Memory#Trigger_Memory_Fill|Memory Fill]] command with both buffers set GSP will only dispatch PSC0.&lt;br /&gt;
&lt;br /&gt;
= Framebuffer Info =&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the top LCD is located at sharedMemBase + 0x200 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the bottom LCD is located at sharedMemBase + 0x240 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
|  Framebuffer info [[GSPGPU:SetBufferSwap|entry]] index&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Flags (bit0 = client has set new data)&lt;br /&gt;
|-&lt;br /&gt;
| 3-2&lt;br /&gt;
| Padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Structure ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Active framebuffer (0 = first, 1 = second)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Left framebuffer VA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Right framebuffer VA (top screen only)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Stride]] (offset 0x90)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| [[GPU/External_Registers#Framebuffer_format|Format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Status]] (offset 0x78)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ? (&amp;quot;Attribute&amp;quot;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a process sets this framebuffer info, it sets index to &amp;lt;nowiki&amp;gt;(index+1) &amp;amp; 1&amp;lt;/nowiki&amp;gt;. Then it writes the framebuffer info entry, and sets flag to value 1. The GSP module loads this framebuffer info entry data into GSP state once the [[GPU]] finishes processing GX commands 3 or 4. Once the GSP module finishes loading this framebuffer info, it sets flag to value 0, then it will not load the framebuffer info again until flag is value 1. After loading this entry data into GSP state, the GSP module then writes this framebuffer state to the [[LCD]] registers. GSP module automatically updates the LCD framebuffer registers each time GX commands 3 or 4 finish, even when this shared memory data was not updated by the application.(GSP module toggles the active framebuffer register when automatically updating LCD registers, when shared memory data is not used)&lt;br /&gt;
&lt;br /&gt;
The two 0x1C-byte framebuffer info entries are located at framebufferinfo+4.&lt;br /&gt;
&lt;br /&gt;
= 3D Slider and 3D [[GSPGPU:SetLedForceOff|LED]] =&lt;br /&gt;
&lt;br /&gt;
See [[Configuration Memory]].&lt;br /&gt;
&lt;br /&gt;
= GX Command Queue =&lt;br /&gt;
&lt;br /&gt;
This command queue is located at sharedMemBase + 0x800 + (clientID * 0x200). It consists of an header followed by at most 15 command entries.&lt;br /&gt;
&lt;br /&gt;
The queue header has the following structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Index of the command to process, this is incremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Total commands to process, this is incremented by the application when adding the command to the queue, and decremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Status (bit0 = halted, bit7 = fatal error)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When bit0 is set, further processing of commands is halted until the client resets the flag and calls [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 7-4&lt;br /&gt;
| Result code for the last command which failed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
After adding a command, [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]] must be used to start command processing (official code does so when the total commands field is 1).&lt;br /&gt;
&lt;br /&gt;
GSP checks for status.bit0 and optionally avoids handling further commands, however the check is done by equality, which means it will always fail if status.bit7 is also set (and thus other commands will be processed). This bug prevents the halting logic from working propertly, but can be worked around by keeping bit0 of word3 set, as that will force halting on each iteration.&lt;br /&gt;
&lt;br /&gt;
== Commands ==&lt;br /&gt;
&lt;br /&gt;
A command entry is made of 8 words. The first word is the command header, subsequent words represent command specific parameters.&lt;br /&gt;
&lt;br /&gt;
The command header has the following structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| When bit0 is set, GSP stops processing further commands (can be used for packing together sets of commands)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When set, the command fails if GSP is busy handling any other command; otherwise, it only fails if GSP is busy handling a command of the same kind&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Addresses specified in command parameters are virtual addresses. Depending on the command, there might be constraints on the accepted parameters. In general, some commands require parameters to be aligned, and addresses are expected to be on [[Memory_Management#Memory_Mapping|linear]], [[Memory_layout#0x1F000000_.28New_3DS_only.29|QTM]] or VRAM memory.&lt;br /&gt;
&lt;br /&gt;
=== Trigger DMA Request ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x00)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush source (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command issues a [[Corelink_DMA_Engines|DMA request]] as the process with [[GSPGPU:AcquireRight|rendering rights]]. When the destination address is within VRAM, GSP places itself as the destination process: this makes it possible to transfer data in VRAM without needing it listed in the destination process [[NCCH/Extended_Header#ARM11_Kernel_Capabilities|exheader mappings]]. Otherwise, both source and destination of the DMA request are the process with rendering rights.&lt;br /&gt;
&lt;br /&gt;
The source buffer must be mapped as readable in the source process, while the destination address must be mapped as writable in the destination process, otherwise GSP calls [[SVC|svcBreak]]. When flushing is enabled and the source address is above VRAM, svcFlushProcessDataCache is used to flush the source buffer.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the command does nothing.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Command List Processing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x01)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Update gas additive blend results (0 = don&#039;t update, 1 = update)&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush buffer (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Command_List|Command List registers]], and optionally updates gas additive blend results after command processing has ended.&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Address and size should be both aligned to 8 bytes, and the address should be in linear, QTM or VRAM memory, otherwise PA 0 is used. When flushing is enabled, [[SVC|svcFlushProcessDataCache]] is used to flush the buffer on the process that has acquired rendering rights.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the command does nothing.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Memory Fill ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x02)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer 0 start address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer 0 value&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buffer 0 end address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buffer 1 start address&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buffer 1 value&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buffer 1 end address&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Control0 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (Control1 &amp;lt;&amp;lt; 16)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Memory_Fill|Memory Fill registers]].&lt;br /&gt;
&lt;br /&gt;
Addresses should be aligned to 8 bytes and must be in linear, QTM or VRAM memory, otherwise error 0xE0E02BF5 (GSP_INVALID_ADDRESS) is returned. The start address for a buffer must be below its end address, else the same error is returned. If the start address for a buffer is 0, that buffer is skipped; otherwise, its relative PSC unit is used for the fill operation.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Display Transfer ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x03)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Source dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Output dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Flags&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Transfer_Engine|Display Transfer registers]].&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Addresses should be aligned to 8 bytes and should be in linear, QTM or VRAM memory, otherwise PA 0 is used.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Texture Copy ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x04)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Line width &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (gap &amp;lt;&amp;lt; 16)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Same as above, for the destination&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Flags&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#TextureCopy|Texture Copy registers]]. Note that GSP doesn&#039;t enforce bit3 of the flags to be set.&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Addresses and size should be aligned to 8 bytes, and the addresses should be in linear, QTM or VRAM memory, otherwise PA 0 is used.&lt;br /&gt;
&lt;br /&gt;
=== Flush Cache Regions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x05)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer 0 address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer 0 size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buffer 1 address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buffer 1 size&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buffer 2 address&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buffer 2 size&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command calls svcFlushProcessDataCache for each buffer on the process that has acquired rendering rights.&lt;br /&gt;
&lt;br /&gt;
If any call fails, its error is returned; If any buffer has size 0, the buffer is skipped. In both cases, subsequent buffers are not processed.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the error 0xD8202A06 (GSP_NO_RIGHT) is returned.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23492</id>
		<title>GSP Shared Memory</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23492"/>
		<updated>2025-05-19T18:55:49Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Fix misinformation&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the structure of the GSP [[GSPGPU:RegisterInterruptRelayQueue|shared]] memory. Interrupt, framebuffer, and GX command data is stored here.&lt;br /&gt;
&lt;br /&gt;
=Interrupt Queue=&lt;br /&gt;
&lt;br /&gt;
The Interrupt queue is located at sharedMemBase + (clientID * 0x40).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| Offset from the count where to save incoming interrupts&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| Count (max 0x20 for PDC, 0x34 for others)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| Missed other interrupts (set to 1 when 0 and count &amp;gt;= 0x34)&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| Flags (bit0 = skip PDC)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4-0x7&lt;br /&gt;
| Missed PDC0 (incremented when flags.bit0 is clear and count &amp;gt;= 0x20) &lt;br /&gt;
|-&lt;br /&gt;
| 0x8-0xB&lt;br /&gt;
| Missed PDC1 (same as above)&lt;br /&gt;
|-&lt;br /&gt;
| 0xC-0x3F&lt;br /&gt;
| Interrupt list (u8) (0=PSC0, 1=PSC1, 2=PDC0/VBlankTop, 3=PDC1/VBlankBottom, 4=PPF, 5=P3D, 6=DMA)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP fills the interrupt list, then triggers the event set with [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]] for the specified process(es).&lt;br /&gt;
&lt;br /&gt;
PDC interrupts are sent to all processes; other interrupts are only sent to the process with rendering rights.&lt;br /&gt;
&lt;br /&gt;
When issuing a [[GSP_Shared_Memory#Trigger_Memory_Fill|Memory Fill]] command with both buffers set GSP will only dispatch PSC0.&lt;br /&gt;
&lt;br /&gt;
= Framebuffer Info =&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the top LCD is located at sharedMemBase + 0x200 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the bottom LCD is located at sharedMemBase + 0x240 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
|  Framebuffer info [[GSPGPU:SetBufferSwap|entry]] index&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Flags (bit0 = client has set new data)&lt;br /&gt;
|-&lt;br /&gt;
| 3-2&lt;br /&gt;
| Padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Structure ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Active framebuffer (0 = first, 1 = second)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Left framebuffer VA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Right framebuffer VA (top screen only)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Stride]] (offset 0x90)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| [[GPU/External_Registers#Framebuffer_format|Format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Status]] (offset 0x78)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ? (&amp;quot;Attribute&amp;quot;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a process sets this framebuffer info, it sets index to &amp;lt;nowiki&amp;gt;(index+1) &amp;amp; 1&amp;lt;/nowiki&amp;gt;. Then it writes the framebuffer info entry, and sets flag to value 1. The GSP module loads this framebuffer info entry data into GSP state once the [[GPU]] finishes processing GX commands 3 or 4. Once the GSP module finishes loading this framebuffer info, it sets flag to value 0, then it will not load the framebuffer info again until flag is value 1. After loading this entry data into GSP state, the GSP module then writes this framebuffer state to the [[LCD]] registers. GSP module automatically updates the LCD framebuffer registers each time GX commands 3 or 4 finish, even when this shared memory data was not updated by the application.(GSP module toggles the active framebuffer register when automatically updating LCD registers, when shared memory data is not used)&lt;br /&gt;
&lt;br /&gt;
The two 0x1C-byte framebuffer info entries are located at framebufferinfo+4.&lt;br /&gt;
&lt;br /&gt;
= 3D Slider and 3D [[GSPGPU:SetLedForceOff|LED]] =&lt;br /&gt;
&lt;br /&gt;
See [[Configuration Memory]].&lt;br /&gt;
&lt;br /&gt;
= Command Queue =&lt;br /&gt;
&lt;br /&gt;
The command queue is located at sharedMemBase + 0x800 + (clientID * 0x200). It consists of an header followed by at most 15 command entries. Each command entry is of size 0x20 and has an header followed by command specific parameters.&lt;br /&gt;
&lt;br /&gt;
After adding a command, [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]] must be used to start command processing (official code does so when the total commands field is 1).&lt;br /&gt;
&lt;br /&gt;
== Command Queue Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Index of the command to process, this is incremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Total commands to process, this is incremented by the application when adding the command to the queue, and decremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Status (bit0 = halted, bit7 = fatal error)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When bit0 is set, further processing of commands is halted until the client resets the flag and calls [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 7-4&lt;br /&gt;
| Result code for the last command which failed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP checks for status.bit0 and optionally avoids handling further commands, however the check is done by equality, which means it will always fail if status.bit7 is also set (and thus other commands will be processed). This bug prevents the halting logic from working propertly, but can be worked around by keeping bit0 of word3 set, as that will force halting on each iteration.&lt;br /&gt;
&lt;br /&gt;
== Command Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| When bit0 is set, GSP stops processing further commands (can be used for packing together sets of commands)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When set, the command fails if GSP is busy handling any other command; otherwise, it only fails if GSP is busy handling a command of the same kind&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Commands ==&lt;br /&gt;
&lt;br /&gt;
Addresses specified in parameters are virtual addresses. Depending on the command, there might be constraints on the accepted parameters. In general, some commands require parameters to be aligned, and addresses are expected to be on [[Memory_Management#Memory_Mapping|linear]], [[Memory_layout#0x1F000000_.28New_3DS_only.29|QTM]] or VRAM memory.&lt;br /&gt;
&lt;br /&gt;
=== Trigger DMA Request ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x00)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush source (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command issues a [[Corelink_DMA_Engines|DMA request]] as the process with [[GSPGPU:AcquireRight|rendering rights]]. When the destination address is within VRAM, GSP places itself as the destination process: this makes it possible to transfer data in VRAM without needing it listed in the destination process [[NCCH/Extended_Header#ARM11_Kernel_Capabilities|exheader mappings]]. Otherwise, both source and destination of the DMA request are the process with rendering rights.&lt;br /&gt;
&lt;br /&gt;
The source buffer must be mapped as readable in the source process, while the destination address must be mapped as writable in the destination process, otherwise GSP calls [[SVC|svcBreak]]. When flushing is enabled and the source address is above VRAM, svcFlushProcessDataCache is used to flush the source buffer.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the command does nothing.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Command List Processing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x01)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Update gas additive blend results (0 = don&#039;t update, 1 = update)&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush buffer (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Command_List|Command List registers]], and optionally updates gas additive blend results after command processing has ended.&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Address and size should be both aligned to 8 bytes, and the address should be in linear, QTM or VRAM memory, otherwise PA 0 is used. When flushing is enabled, [[SVC|svcFlushProcessDataCache]] is used to flush the buffer on the process that has acquired rendering rights.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the command does nothing.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Memory Fill ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x02)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer 0 start address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer 0 value&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buffer 0 end address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buffer 1 start address&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buffer 1 value&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buffer 1 end address&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Control0 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (Control1 &amp;lt;&amp;lt; 16)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Memory_Fill|Memory Fill registers]].&lt;br /&gt;
&lt;br /&gt;
Addresses should be aligned to 8 bytes and must be in linear, QTM or VRAM memory, otherwise error 0xE0E02BF5 (GSP_INVALID_ADDRESS) is returned. The start address for a buffer must be below its end address, else the same error is returned. If the start address for a buffer is 0, that buffer is skipped; otherwise, its relative PSC unit is used for the fill operation.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Display Transfer ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x03)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Source dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Output dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Flags&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Transfer_Engine|Display Transfer registers]].&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Addresses should be aligned to 8 bytes and should be in linear, QTM or VRAM memory, otherwise PA 0 is used.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Texture Copy ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x04)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Line width &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (gap &amp;lt;&amp;lt; 16)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Same as above, for the destination&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Flags&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#TextureCopy|Texture Copy registers]]. Note that GSP doesn&#039;t enforce bit3 of the flags to be set.&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Addresses and size should be aligned to 8 bytes, and the addresses should be in linear, QTM or VRAM memory, otherwise PA 0 is used.&lt;br /&gt;
&lt;br /&gt;
=== Flush Cache Regions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x05)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer 0 address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer 0 size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buffer 1 address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buffer 1 size&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buffer 2 address&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buffer 2 size&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command calls svcFlushProcessDataCache for each buffer on the process that has acquired rendering rights.&lt;br /&gt;
&lt;br /&gt;
If any call fails, its error is returned; If any buffer has size 0, the buffer is skipped. In both cases, subsequent buffers are not processed.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the error 0xD8202A06 (GSP_NO_RIGHT) is returned.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23491</id>
		<title>GPU/External Registers</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23491"/>
		<updated>2025-05-17T19:57:10Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the address range accessible from the ARM11, used to configure the basic GPU functionality. For information about the internal registers used for 3D rendering, see [[GPU/Internal Registers]].&lt;br /&gt;
&lt;br /&gt;
== Map ==&lt;br /&gt;
Address mappings for the external registers. GSPGPU:WriteHWRegs takes these addresses relative to 0x1EB00000. &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! User VA&lt;br /&gt;
! PA&lt;br /&gt;
! Length&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00000&lt;br /&gt;
| 0x10400000&lt;br /&gt;
| 4&lt;br /&gt;
| Hardware ID&lt;br /&gt;
| Bit2: new model&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00004&lt;br /&gt;
| 0x10400004&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00010&lt;br /&gt;
| 0x10400010&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill1]] &amp;quot;PSC0&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00020&lt;br /&gt;
| 0x10400020&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill2]] &amp;quot;PSC1&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00030&lt;br /&gt;
| 0x10400030&lt;br /&gt;
| 4&lt;br /&gt;
| VRAM bank control&lt;br /&gt;
| Bits 8-11 = bank[i] disabled; other bits are unused.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00034&lt;br /&gt;
| 0x10400034&lt;br /&gt;
| 4&lt;br /&gt;
| GPU Busy&lt;br /&gt;
| Bit26 = PSC0, bit27 = PSC1, Bit30 = PPF, Bit31 = P3D&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00050&lt;br /&gt;
| 0x10400050&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x22221200 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00054&lt;br /&gt;
| 0x10400054&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFF2 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000C0&lt;br /&gt;
| 0x104000C0&lt;br /&gt;
| 4&lt;br /&gt;
| Backlight control&lt;br /&gt;
| Writes 0x0 to allow backlights to turn off, 0x20000000 to force them always on.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00400&lt;br /&gt;
| 0x10400400&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC0&amp;quot; (top screen)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00500&lt;br /&gt;
| 0x10400500&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC1&amp;quot; (bottom)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| 0x10400C00&lt;br /&gt;
| ?&lt;br /&gt;
| [[#Transfer_Engine|Transfer Engine]] &amp;quot;DMA&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|colspan=&amp;quot;5&amp;quot;| 0x1EF01000/0x10401000 - 0x1EF01C00/0x10401C00 maps to [[GPU/Internal_Registers|GPU internal registers]]. These registers are usually not read/written directly here, but are written using the command list interface below (corresponding to the GPUREG_CMDBUF_* internal registers)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01000&lt;br /&gt;
| 0x10401000&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0 on GPU init and before the Command List is used&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01080&lt;br /&gt;
| 0x10401080&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x12345678 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010C0&lt;br /&gt;
| 0x104010C0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFFFFFFF0 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010D0&lt;br /&gt;
| 0x104010D0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 1 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF014??&lt;br /&gt;
| 0x104014??&lt;br /&gt;
| 0x14&lt;br /&gt;
| &amp;quot;PPF&amp;quot; ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| 0x104018E0&lt;br /&gt;
| 0x14&lt;br /&gt;
| [[#Command_List|Command List]] &amp;quot;P3D&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Memory Fill ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  User VA&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X0&lt;br /&gt;
| Buffer start physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X4&lt;br /&gt;
| Buffer end physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X8&lt;br /&gt;
| Fill value&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000XC&lt;br /&gt;
| Control. bit0: start/busy, bit1: finished, bit8-9: fill-width (0=16bit, 1=3=24bit, 2=32bit)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Memory fills are used to initialize buffers in memory with a given value, similar to memset. A memory fill is triggered by setting bit0 in the control register. Doing so aborts any running memory fills on that filling unit. Upon completion, the hardware unsets bit0 and sets bit1 and fires interrupt PSC0.&lt;br /&gt;
&lt;br /&gt;
The addresses must not be part of FCRAM.&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP Shared Memory#GX SetMemoryFill|GX SetMemoryFill]].&lt;br /&gt;
&lt;br /&gt;
== LCD Source Framebuffer Setup ==&lt;br /&gt;
&lt;br /&gt;
All of these registers must be accessed with 32bit operations regardless of the registers&#039; actual bit size.&lt;br /&gt;
&lt;br /&gt;
The naming of these parameters reflects the physical characteristics of the displays, and not the way the 3DS is normally held.&lt;br /&gt;
&lt;br /&gt;
To make sense of these values, the 3DS must be held in a way, so that the bottom screen is in the left hand, and the top screen is in the right hand, and that way the first pixel will be in the top-left corner, as it should be. If the 3DS is held normally, the first pixel is in the bottom-left corner.&lt;br /&gt;
&lt;br /&gt;
All pixel and scanline timing values are 12bits, unless noted. This also applies to those fields where two u16 are combined into one register. Each u16 field is only 12bits in size. timin&lt;br /&gt;
&lt;br /&gt;
The horizontal timing parameter order is as follows (values may overflow through HTotal register value):&lt;br /&gt;
 0x10 &amp;lt; 0x14 &amp;lt;= 0x60.LO &amp;lt;= 0x04 &amp;lt;= 0x60.HI &amp;lt;= 0x08 &amp;lt;= 0x0C &amp;lt;= 0x10&lt;br /&gt;
 0x18 &amp;lt;= 0x60.LO&lt;br /&gt;
&lt;br /&gt;
Timing starts from HCount == 0, then each absolute value in the beforementioned register chain triggers when HCount == register, latching the primitive display controller into a new mode.&lt;br /&gt;
There is an inherent latch order, where if two simultenaous events occur, one event wins over another.&lt;br /&gt;
&lt;br /&gt;
 Known latched modes (in order):&lt;br /&gt;
 - HSync (triggers a line to the LCD to move to the next line)&lt;br /&gt;
 - Back porch (area between HSync and border being displayed, no pixels pushed, min 16 pixel clocks, otherwise the screen gets glitchy)&lt;br /&gt;
 - Left border start (no image data is being displayed, just a configurable solid color)&lt;br /&gt;
 - Image start (pixel data is being DMA&#039;d from video memory or main RAM)&lt;br /&gt;
 - Right border start/Image end (border color is being displayed after the main image)&lt;br /&gt;
 - Unknown synchronization (supposed to be probably right border end, but this mode seems to be broken or not do anything)&lt;br /&gt;
 - Front porch (no pixels pushed, 68 clock min, otherwise the screen doesn&#039;t sync properly, and really glitches out)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Offset&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| HTotal&lt;br /&gt;
| The total width of a timing scanline. In other words, this is the horizontal refresh clock divider value.&lt;br /&gt;
&lt;br /&gt;
HClock = PClock / (HTotal + 1)&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| HStart&lt;br /&gt;
| Determines when the image is going to be displayed in the visible region (register 0x60).&lt;br /&gt;
|-&lt;br /&gt;
| 0x08&lt;br /&gt;
| HBR&lt;br /&gt;
| Right border start(?). Does nothing.&lt;br /&gt;
&lt;br /&gt;
While this register seems to have no impact on the image whatsoever, it still has to be set to a valid value.&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| 0x0C&lt;br /&gt;
| HPF&lt;br /&gt;
| Front porch. The image is blanked during this period, and no pixels are pushed to the LCD.&lt;br /&gt;
&lt;br /&gt;
Unknown why, but a single dot of red is displayed before entering this mode.&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| HSync&lt;br /&gt;
| Triggers a HSync pulse.&lt;br /&gt;
&lt;br /&gt;
Based on behavior, this needs to last at least a pixel clock for the LCD to register the sync.&lt;br /&gt;
|-&lt;br /&gt;
| 0x14&lt;br /&gt;
| HPB&lt;br /&gt;
| Back porch? Has to be at least one bigger than HSync, otherwise HSync never triggers.&lt;br /&gt;
&lt;br /&gt;
The display is blank, and the LCD displays nothing in this period (doesn&#039;t push pixels).&lt;br /&gt;
|-&lt;br /&gt;
| 0x18&lt;br /&gt;
| HBL&lt;br /&gt;
| Left border trigger treshold. Enables pushing pixels to the display.&lt;br /&gt;
&lt;br /&gt;
If this value is smaller than the back porch, then the back porch period will be zero, and the border will be immediately displayed upon entering the back porch period.&lt;br /&gt;
&lt;br /&gt;
Can be lower than HSync, as the back porch is what takes the controller out of HSync.&lt;br /&gt;
&lt;br /&gt;
Must be &amp;lt;= HDisp start (reg 0x60 low u16), otherwise no pixels will be pushed due to a glitched state.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1C&lt;br /&gt;
| H Interrupt timing&lt;br /&gt;
| Made up from two u16 values, PDC interrupt line is asserted when HCount == low u16, and most likely deasserted when HCount == high u16.&lt;br /&gt;
&lt;br /&gt;
There seems to be some limitations though:&lt;br /&gt;
* low u16 must be smaller than high u16&lt;br /&gt;
* if low u16 is less than HTotal then high u16 must also be smaller than HTotal&lt;br /&gt;
* setting low u16 to &amp;gt;= HTotal disables the interrupt ever firing&lt;br /&gt;
&lt;br /&gt;
This is configured by gsp in a way so that low u16 equals to HTotal, meaning the HSync interrupt will never fire.&lt;br /&gt;
|-&lt;br /&gt;
| 0x20&lt;br /&gt;
| low u16: ???&lt;br /&gt;
high u16: ???&lt;br /&gt;
| ???&lt;br /&gt;
|-&lt;br /&gt;
| 0x24&lt;br /&gt;
| VTotal&lt;br /&gt;
| Total height of the timing window. Can be interpreted as the vertical clock divider.&lt;br /&gt;
&lt;br /&gt;
VClock = PClock / (HTotal + 1) / (VTotal + 1)&lt;br /&gt;
&lt;br /&gt;
Setting this to 494 lowers framerate to about 50.040660858 Hz ((268111856 / 24) / (450 + 1) / (494 + 1)).&lt;br /&gt;
|-&lt;br /&gt;
| 0x28&lt;br /&gt;
| ?&lt;br /&gt;
| Seems to determine the vertical blanking interval.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Setting this to lower than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will cut off the top &amp;lt;code&amp;gt;VTotal - VDisp - thisvalue&amp;lt;/code&amp;gt; lines.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will make the image be pushed downwards with the overscan color visible.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;HTotal&amp;lt;/code&amp;gt; will make the GPU skip vertical pixel data synchronization (hence filling the screen with the rest of the pixel data past the given screen framebuffer size). Also will skip &amp;lt;code&amp;gt;thisvalue + somevalue - HTotal&amp;lt;/code&amp;gt; lines into the &amp;quot;global&amp;quot; pixel buffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x30&lt;br /&gt;
| ?&lt;br /&gt;
| Total amount of vertical scanlines in the pixel buffer, must be bigger than *an unknown blanking-like value*. If this value is less than VDisp then the last two scanlines will be repeated interlaced until VDisp is reached.&lt;br /&gt;
|-&lt;br /&gt;
| 0x34&lt;br /&gt;
| VDisp(?)&lt;br /&gt;
| Total amonut of vertical scanlines displayed (only for top screen it seems like). If this value is less than VTotal then the rest of the scanlines will not be updated on the screen, so those will slowly fade out. Must be bigger than *an unknown blanking-like value*, otherwise an underflow will happen.&lt;br /&gt;
|-&lt;br /&gt;
| 0x38&lt;br /&gt;
| Vertical data offset(?)&lt;br /&gt;
| ??? Seems to offset the screen upwards if this value is high enough. If this value is higher or equal to *some value* (aka. if less than one scanline is displayed on the screen) then the screen will lose synchronization.&lt;br /&gt;
|-&lt;br /&gt;
| 0x40&lt;br /&gt;
| V Interrupt timing&lt;br /&gt;
| Similar to H Interrupt timing (0x1C), except the comparison is done against VCount, the limitations are emposed on VTotal, and the interrupt that fires is VSync.&lt;br /&gt;
&lt;br /&gt;
One important note is that it seems like the VSync interrupt always fires at HCount == 0, and there doesn&#039;t seem to be a register to control this behavior.&lt;br /&gt;
|-&lt;br /&gt;
| 0x44&lt;br /&gt;
| ???&lt;br /&gt;
| similar functionality to 0x10&lt;br /&gt;
|-&lt;br /&gt;
| 0x48&lt;br /&gt;
| ???&lt;br /&gt;
| bit0 seems to disable HSync, bit8 seems to disable VSync, rest of the bits aren&#039;t writable.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4C&lt;br /&gt;
| Overscan filler color&lt;br /&gt;
| 24bits(? top 8bits ignored)&lt;br /&gt;
&lt;br /&gt;
When the visible region is being drawn, but the timing parameters are set up in a way that the framebuffer is smaller than the visible region, it will be filled by this color.&lt;br /&gt;
|-&lt;br /&gt;
| 0x50&lt;br /&gt;
| HCount&lt;br /&gt;
| Horizontal &amp;quot;beam position&amp;quot; counter. Note that this value does not equal to the current pixel being drawn.&lt;br /&gt;
|-&lt;br /&gt;
| 0x54&lt;br /&gt;
| VCount&lt;br /&gt;
| Vertical &amp;quot;beam position&amp;quot; counter. Note that the scanline being drawn isn&#039;t equal to this value.&lt;br /&gt;
|-&lt;br /&gt;
| 0x5C&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: Image width (including some offset?)&lt;br /&gt;
high u16: Image height??? (seems to be unused)&lt;br /&gt;
|-&lt;br /&gt;
| 0x60&lt;br /&gt;
| HDisp&lt;br /&gt;
| low u16: Image start (border --&amp;gt; pixel data)&lt;br /&gt;
high u16: Image end (pixel data --&amp;gt; border)&lt;br /&gt;
|-&lt;br /&gt;
| 0x64&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: unknown&lt;br /&gt;
high u16: framebuffer total height (amount of scanlines blitted regardless of framebuffer height)&lt;br /&gt;
|-&lt;br /&gt;
| 0x68&lt;br /&gt;
| Framebuffer A first address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x6C&lt;br /&gt;
| Framebuffer A second address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x70&lt;br /&gt;
| Framebuffer format and other settings&lt;br /&gt;
| See [[#Framebuffer_format|framebuffer format]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x74&lt;br /&gt;
| PDC control&lt;br /&gt;
| Bit 0: Enable display controller.&lt;br /&gt;
Bit 8: HBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 9: VBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 10: Error IRQ mask? (0 = enabled).&lt;br /&gt;
Bit 16: Output enable?&lt;br /&gt;
|-&lt;br /&gt;
| 0x78&lt;br /&gt;
| Framebuffer select and status&lt;br /&gt;
| Bit 0: Next framebuffer to display (after VBlank).&lt;br /&gt;
Bit 4: Currently displaying framebuffer?&lt;br /&gt;
Bit 8: Reset FIFO?&lt;br /&gt;
Bit 16: HBlank IRQ status/ack. Write 1 to aknowledge.&lt;br /&gt;
Bit 17: VBlank IRQ status/ack.&lt;br /&gt;
Bit 18: Error IRQ status/ack?&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| Color lookup table index select&lt;br /&gt;
| 8bits, write-only&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| Color lookup table indexed element&lt;br /&gt;
| Contains the value of the color lookup table indexed by the above register, 24bits, RGB8 (0x00BBGGRR)  &lt;br /&gt;
Accessing this register will increase the index register by one&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| Framebuffer stride&lt;br /&gt;
| 32bits (bottom 3bits ignored?)&lt;br /&gt;
&lt;br /&gt;
Distance in bytes between the start of two framebuffer rows (must be a multiple of 8).&lt;br /&gt;
&lt;br /&gt;
In other words, this can be interpreted as the amount to add to the framebuffer pointer after displaying a scanline.&lt;br /&gt;
&lt;br /&gt;
Setting this to zero will cause only the first line of the image to be displayed repeated on the entire display. With the HSync interrupt it&#039;s possible to &amp;quot;race the beam&amp;quot; to (ab)use this feature.&lt;br /&gt;
&lt;br /&gt;
Because of this simplicity, writing a negative value here VFlips the image, although that requires the framebuffer pointer register to be set to the start of the last scanline, instead of at the start of the framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| Framebuffer B first address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| Framebuffer B second address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer format ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 2-0&lt;br /&gt;
| [[#Framebuffer_color_formats|Color format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5-4&lt;br /&gt;
| Framebuffer interlacing mode&lt;br /&gt;
&lt;br /&gt;
 0 - A  (no interlacing)&lt;br /&gt;
 1 - AA (scanline doubling)&lt;br /&gt;
 2 - AB (interlace enable)&lt;br /&gt;
 3 - BA (same as above, but the fields are inverted)&lt;br /&gt;
&lt;br /&gt;
In AB and BA interlace modes, a scanline from each framebuffer is output in an alternating manner. In AB mode, Framebuffer A is output on the frist display scanline. Similarly, in BA mode, Framebuffer B gets output to the first display scanline.&lt;br /&gt;
&lt;br /&gt;
The way AB and BA modes work, is that a scanline is output, the framebuffer stride value is added to the internal scanline pointer value, and the other framebuffer is selected. And this alternates until the end of the draw region.&lt;br /&gt;
&lt;br /&gt;
AA interlacing works like AB interlacing, except both internal framebuffer pointers are set to the Framebuffer A pointer value.&lt;br /&gt;
&lt;br /&gt;
In A mode (no interlacing), it doesn&#039;t switch to the other framebuffer at the end of outpuitting a scanline to the display.&lt;br /&gt;
&lt;br /&gt;
Bottom screen has this set to 0 (A mode, no interlacing) at all times.  &lt;br /&gt;
Top screen uses AB interlacing in 3D mode (with 3D slider enabled), and A mode (no interlacing) in 2D mode.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Alternative pixel output mode*&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 9-8&lt;br /&gt;
| DMA size&lt;br /&gt;
&lt;br /&gt;
 0 -  4 FCRAM words (32 bytes)&lt;br /&gt;
 1 -  8 FCRAM words (64 bytes)&lt;br /&gt;
 2 - 16 FCRAM words (128 bytes)&lt;br /&gt;
 3 - ???&lt;br /&gt;
&lt;br /&gt;
FCRAM doesn&#039;t support DMA size 3, as it can only burst up to 16 words (128 bytes), and will show a black screen instead.&lt;br /&gt;
|-&lt;br /&gt;
| 31-16&lt;br /&gt;
| Unknown&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt; The weird thing about bit6, is that it works different between the bottom and top LCD. On the bottom LCD, it doubles the number of outputted pixels (so the same pixel is outputted twice, effectively doing pixel/column doubling). However on the top screen, it does scanline doubling instead.  &lt;br /&gt;
Most likely the top screen receives two pixels at once per clock unit, outputting two scanlines simultaneously.&lt;br /&gt;
&lt;br /&gt;
On a 2DS, it seems to have no effect on the top part of the display, and on the bottom screen it just shifts the framebuffer to the right two pixels.&lt;br /&gt;
&lt;br /&gt;
GSP module only allows the LCD stereoscopy (3D) to be enabled when bit5=1 and bit6=0 here. When GSP module updates this register, GSP module will automatically disable the stereoscopy if those bits are not set for enabling stereoscopy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When both interlacing and alternative mode is disabled (bit6=0), the full resolution of the top screen (240x800) can be utilized if the PDC registers are updated to accomodate this higher resolution. GSP contains tables for this mode (gsp mode == 1). GSP automatically applies this mode if both bit5 and bit6 are cleared. This is also the default, and the only valid mode for the bottom screen in userland.&lt;br /&gt;
&lt;br /&gt;
If only AB interlacing is enabled (bit5=1, bit6=0), gsp detects this as a request to switch to 3D mode (gsp mode == 2), and enables the parallax barrier.  &lt;br /&gt;
It&#039;s unknown how to control this, but some other PDC registers control if interlacing should be done by true interleaving (both framebuffers are treated as 240x400), or by skipping lines (both framebuffers are treated as 240x800).&lt;br /&gt;
&lt;br /&gt;
If only alternative mode is enabled (bit5=0, bit6=1), gsp detects it as a request to switch back to 2D mode for the top screen (gsp mode == 0). This is also the default mode for the top screen.&lt;br /&gt;
&lt;br /&gt;
Both interlacing and scan doubling can&#039;t be enabled in usermode, but it works as expected in baremetal.&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer color formats ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Value&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| GL_RGBA8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| GL_RGB8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| GL_RGB565_OES&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| GL_RGB5_A1_OES&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| GL_RGBA4_OES&lt;br /&gt;
|}&lt;br /&gt;
Color components are laid out in reverse byte order, with the most significant bits used first (i.e. non-24-bit pixels are stored as a little-endian values). For instance, a raw data stream of two GL_RGB565_OES pixels looks like GGGBBBBB RRRRRGGG GGGBBBBB RRRRRGGG.&lt;br /&gt;
&lt;br /&gt;
Color formats 5, 6, and 7 are blocked by gsp, but they behave as pixel-doubled RGBA8 (not line doubling, but instead the same pixel is output twice) if used outside of userland.&lt;br /&gt;
&lt;br /&gt;
== Transfer Engine ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| Input physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C04&lt;br /&gt;
| Output physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C08&lt;br /&gt;
| DisplayTransfer output width (bits 0-15) and height (bits 16-31).&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C0C&lt;br /&gt;
| DisplayTransfer input width and height.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C10&lt;br /&gt;
| Transfer flags. (See below)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C14&lt;br /&gt;
| GSP module writes value 0 here prior to writing to 0x1EF00C18, for cmd3.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C18&lt;br /&gt;
|  Setting bit0 starts the transfer. Upon completion, bit0 is unset and bit8 is set.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C1C&lt;br /&gt;
|  ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C20&lt;br /&gt;
| TextureCopy total amount of data to copy, in bytes.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C24&lt;br /&gt;
| TextureCopy input line width (bits 0-15) and gap (bits 16-31), in 16 byte units.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C28&lt;br /&gt;
| TextureCopy output line width and gap.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP_Shared_Memory|GX command]] 3 and 4. For cmd4, *0x1EF00C18 |= 1 is used instead of just writing value 1. The DisplayTransfer registers are only used if bit 3 of the flags is unset and ignored otherwise. The TextureCopy registers are likewise only used if bit 3 is set, and ignored otherwise.&lt;br /&gt;
&lt;br /&gt;
The minimum supported dimension for output is 64x64, anything lower will hang the engine.&lt;br /&gt;
&lt;br /&gt;
==== Flags Register - 0x1EF00C10 ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| When set, the framebuffer data is flipped vertically.&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| When set, the input framebuffer is treated as linear and converted to tiled in the output, converts tiled-&amp;gt;linear when unset.&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| This bit is required when the output width is less than the input width for the hardware to properly crop the lines, otherwise the output will be mis-aligned.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Uses a TextureCopy mode transfer. See below for details.&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Don&#039;t perform tiled-linear conversion. Incompatible with bit 1, so only tiled-tiled transfers can be done, not linear-linear.&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 10-8&lt;br /&gt;
| Input framebuffer color format, value0 and value1 are the same as the [[GPU Registers#Framebuffer_color_formats|LCD Source Framebuffer Formats]] (usually zero)&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 14-12&lt;br /&gt;
| Output framebuffer color format&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| Use 32x32 block tiling mode, instead of the usual 8x8 one. Output dimensions must be multiples of 32, even if cropping with bit 2 set above.&lt;br /&gt;
|-&lt;br /&gt;
| 17-23&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 24-25&lt;br /&gt;
| Scale down the input image using a box filter. 0 = No downscale, 1 = 2x1 downscale. 2 = 2x2 downscale, 3 = invalid&lt;br /&gt;
|-&lt;br /&gt;
| 31-26&lt;br /&gt;
| Not writable&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== TextureCopy ===&lt;br /&gt;
&lt;br /&gt;
When bit 3 of the control register is set, the hardware performs a TextureCopy-mode transfer: no format conversions are done, instead a raw data copy is performed from the source to the destination, with a configurable gap between lines. All bits of the control register are ignored, except for input/output dimensions, which are used for line width and gap, and bit 2, which must be set when gaps are used.&lt;br /&gt;
&lt;br /&gt;
The total amount of bytes to copy is specified in the size register, the hardware loops reading lines from the input and writing them to the output until this amount is copied. The gap specifies the number of bytes to skip after each line read (a gap of 0 results in a contiguous read). Gaps do not count towards the total size of the transfer.&lt;br /&gt;
&lt;br /&gt;
When setting line width and gap they must be divided by 2 (it can be thought as the calculation being done in bits, and the values being stripped of their lower 4 bits for the alignment). For example, if the left half of a 32x32 RGB8 texture is to be copied, the parameters will be:&lt;br /&gt;
 line width = (16 * 24) &amp;gt;&amp;gt; 4 = 24&lt;br /&gt;
 gap = line width&lt;br /&gt;
 size = 16 * 32 * 3 = 1536&lt;br /&gt;
&lt;br /&gt;
By correctly calculating the input and output gap sizes it is possible to use this functionality to copy arbitrary sub-rectangles between differently-sized framebuffers or textures, which is one of its main uses over a regular no-conversion DisplayTransfer. When copying tiled textures/framebuffers it&#039;s important to remember that the contents of a tile are laid out sequentially in memory, and so this should be taken into account when calculating the transfer parameters.&lt;br /&gt;
&lt;br /&gt;
Specifying invalid/junk values for the TextureCopy dimensions can result in the GPU hanging while attempting to process this TextureCopy. For instance, when in contiguous mode the size must be at least 16; when in gap mode, the size must be at least 192, and the line width must not be 0.&lt;br /&gt;
&lt;br /&gt;
== Command List ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| Buffer size in bytes &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E8&lt;br /&gt;
| Buffer physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018F0&lt;br /&gt;
| Setting bit0 to 1 enables processing GPU command execution. Upon completion, bit0 seems to be reset to 0.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These 3 registers are used by [[GSP_Shared_Memory|GX command]] 1. This is used for [[GPU/Internal_Registers|GPU commands]].&lt;br /&gt;
&lt;br /&gt;
== Framebuffers ==&lt;br /&gt;
These LCD framebuffers normally contain the last rendered frames from the GPU. The framebuffers are drawn from left-to-right, instead of top-to-bottom.(Thus the beginning of the framebuffer is drawn starting at the left side of the screen)&lt;br /&gt;
&lt;br /&gt;
Both of the 3D screen left/right framebuffers are displayed regardless of the 3D slider&#039;s state, however when the 3D slider is set to &amp;quot;off&amp;quot; the 3D effect is disabled. Normally when the 3D slider&#039;s state is set to &amp;quot;off&amp;quot; the left/right framebuffer addresses are set to the same physical address. When the 3D effect is disabled and the left/right framebuffers are set to separate addresses, the LCD seems to alternate between displaying the left/right framebuffer each frame.&lt;br /&gt;
&lt;br /&gt;
==== Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00400 = 0x1C2&lt;br /&gt;
* 0x1EF00404 = 0xD1&lt;br /&gt;
* 0x1EF00408 = 0x1C1&lt;br /&gt;
* 0x1EF0040C = 0x1C1&lt;br /&gt;
* 0x1EF00410 = 0&lt;br /&gt;
* 0x1EF00414 = 0xCF&lt;br /&gt;
* 0x1EF00418 = 0xD1&lt;br /&gt;
* 0x1EF0041C = 0x1C501C1&lt;br /&gt;
* 0x1EF00420 = 0x10000&lt;br /&gt;
* 0x1EF00424 = 0x19D&lt;br /&gt;
* 0x1EF00428 = 2&lt;br /&gt;
* 0x1EF0042C = 0x1C2&lt;br /&gt;
* 0x1EF00430 = 0x1C2&lt;br /&gt;
* 0x1EF00434 = 0x1C2&lt;br /&gt;
* 0x1EF00438 = 1&lt;br /&gt;
* 0x1EF0043C = 2&lt;br /&gt;
* 0x1EF00440 = 0x1960192&lt;br /&gt;
* 0x1EF00444 = 0&lt;br /&gt;
* 0x1EF00448 = 0&lt;br /&gt;
* 0x1EF0045C = 0x19000F0&lt;br /&gt;
* 0x1EF00460 = 0x1c100d1&lt;br /&gt;
* 0x1EF00464 = 0x1920002&lt;br /&gt;
* 0x1EF00470 = 0x80340&lt;br /&gt;
* 0x1EF0049C = 0&lt;br /&gt;
&lt;br /&gt;
==== More Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00468 = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF0046C = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF00494 = 0x18300000&lt;br /&gt;
* 0x1EF00498 = 0x18300000&lt;br /&gt;
* 0x1EF00478 = 1, doesn&#039;t stay 1, read as 0&lt;br /&gt;
* 0x1EF00474 = 0x10501&lt;br /&gt;
&lt;br /&gt;
[[Category:GPU]]&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23489</id>
		<title>GPU/External Registers</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23489"/>
		<updated>2025-05-12T13:07:08Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: moved&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the address range accessible from the ARM11, used to configure the basic GPU functionality. For information about the internal registers used for 3D rendering, see [[GPU/Internal Registers]].&lt;br /&gt;
&lt;br /&gt;
== Map ==&lt;br /&gt;
Address mappings for the external registers. GSPGPU:WriteHWRegs takes these addresses relative to 0x1EB00000. &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! User VA&lt;br /&gt;
! PA&lt;br /&gt;
! Length&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00000&lt;br /&gt;
| 0x10400000&lt;br /&gt;
| 4&lt;br /&gt;
| Hardware ID&lt;br /&gt;
| Bit2: new model&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00004&lt;br /&gt;
| 0x10400004&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00010&lt;br /&gt;
| 0x10400010&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill1]] &amp;quot;PSC0&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00020&lt;br /&gt;
| 0x10400020&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill2]] &amp;quot;PSC1&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00030&lt;br /&gt;
| 0x10400030&lt;br /&gt;
| 4&lt;br /&gt;
| VRAM bank control&lt;br /&gt;
| Bits 8-11 = bank[i] disabled; other bits are unused.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00034&lt;br /&gt;
| 0x10400034&lt;br /&gt;
| 4&lt;br /&gt;
| GPU Busy&lt;br /&gt;
| Bit26 = PSC0, bit27 = PSC1, Bit30 = PPF, Bit31 = P3D&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00050&lt;br /&gt;
| 0x10400050&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x22221200 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00054&lt;br /&gt;
| 0x10400054&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFF2 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000C0&lt;br /&gt;
| 0x104000C0&lt;br /&gt;
| 4&lt;br /&gt;
| Backlight control&lt;br /&gt;
| Writes 0x0 to allow backlights to turn off, 0x20000000 to force them always on.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00400&lt;br /&gt;
| 0x10400400&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC0&amp;quot; (top screen)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00500&lt;br /&gt;
| 0x10400500&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC1&amp;quot; (bottom)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| 0x10400C00&lt;br /&gt;
| ?&lt;br /&gt;
| [[#Transfer_Engine|Transfer Engine]] &amp;quot;DMA&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|colspan=&amp;quot;5&amp;quot;| 0x1EF01000/0x10401000 - 0x1EF01C00/0x10401C00 maps to [[GPU/Internal_Registers|GPU internal registers]]. These registers are usually not read/written directly here, but are written using the command list interface below (corresponding to the GPUREG_CMDBUF_* internal registers)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01000&lt;br /&gt;
| 0x10401000&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0 on GPU init and before the Command List is used&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01080&lt;br /&gt;
| 0x10401080&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x12345678 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010C0&lt;br /&gt;
| 0x104010C0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFFFFFFF0 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010D0&lt;br /&gt;
| 0x104010D0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 1 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF014??&lt;br /&gt;
| 0x104014??&lt;br /&gt;
| 0x14&lt;br /&gt;
| &amp;quot;PPF&amp;quot; ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| 0x104018E0&lt;br /&gt;
| 0x14&lt;br /&gt;
| [[#Command_List|Command List]] &amp;quot;P3D&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Memory Fill ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  User VA&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X0&lt;br /&gt;
| Buffer start physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X4&lt;br /&gt;
| Buffer end physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X8&lt;br /&gt;
| Fill value&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000XC&lt;br /&gt;
| Control. bit0: start/busy, bit1: finished, bit8-9: fill-width (0=16bit, 1=3=24bit, 2=32bit)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Memory fills are used to initialize buffers in memory with a given value, similar to memset. A memory fill is triggered by setting bit0 in the control register. Doing so aborts any running memory fills on that filling unit. Upon completion, the hardware unsets bit0 and sets bit1 and fires interrupt PSC0.&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP Shared Memory#GX SetMemoryFill|GX SetMemoryFill]].&lt;br /&gt;
&lt;br /&gt;
== LCD Source Framebuffer Setup ==&lt;br /&gt;
&lt;br /&gt;
All of these registers must be accessed with 32bit operations regardless of the registers&#039; actual bit size.&lt;br /&gt;
&lt;br /&gt;
The naming of these parameters reflects the physical characteristics of the displays, and not the way the 3DS is normally held.&lt;br /&gt;
&lt;br /&gt;
To make sense of these values, the 3DS must be held in a way, so that the bottom screen is in the left hand, and the top screen is in the right hand, and that way the first pixel will be in the top-left corner, as it should be. If the 3DS is held normally, the first pixel is in the bottom-left corner.&lt;br /&gt;
&lt;br /&gt;
All pixel and scanline timing values are 12bits, unless noted. This also applies to those fields where two u16 are combined into one register. Each u16 field is only 12bits in size. timin&lt;br /&gt;
&lt;br /&gt;
The horizontal timing parameter order is as follows (values may overflow through HTotal register value):&lt;br /&gt;
 0x10 &amp;lt; 0x14 &amp;lt;= 0x60.LO &amp;lt;= 0x04 &amp;lt;= 0x60.HI &amp;lt;= 0x08 &amp;lt;= 0x0C &amp;lt;= 0x10&lt;br /&gt;
 0x18 &amp;lt;= 0x60.LO&lt;br /&gt;
&lt;br /&gt;
Timing starts from HCount == 0, then each absolute value in the beforementioned register chain triggers when HCount == register, latching the primitive display controller into a new mode.&lt;br /&gt;
There is an inherent latch order, where if two simultenaous events occur, one event wins over another.&lt;br /&gt;
&lt;br /&gt;
 Known latched modes (in order):&lt;br /&gt;
 - HSync (triggers a line to the LCD to move to the next line)&lt;br /&gt;
 - Back porch (area between HSync and border being displayed, no pixels pushed, min 16 pixel clocks, otherwise the screen gets glitchy)&lt;br /&gt;
 - Left border start (no image data is being displayed, just a configurable solid color)&lt;br /&gt;
 - Image start (pixel data is being DMA&#039;d from video memory or main RAM)&lt;br /&gt;
 - Right border start/Image end (border color is being displayed after the main image)&lt;br /&gt;
 - Unknown synchronization (supposed to be probably right border end, but this mode seems to be broken or not do anything)&lt;br /&gt;
 - Front porch (no pixels pushed, 68 clock min, otherwise the screen doesn&#039;t sync properly, and really glitches out)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Offset&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| HTotal&lt;br /&gt;
| The total width of a timing scanline. In other words, this is the horizontal refresh clock divider value.&lt;br /&gt;
&lt;br /&gt;
HClock = PClock / (HTotal + 1)&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| HStart&lt;br /&gt;
| Determines when the image is going to be displayed in the visible region (register 0x60).&lt;br /&gt;
|-&lt;br /&gt;
| 0x08&lt;br /&gt;
| HBR&lt;br /&gt;
| Right border start(?). Does nothing.&lt;br /&gt;
&lt;br /&gt;
While this register seems to have no impact on the image whatsoever, it still has to be set to a valid value.&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| 0x0C&lt;br /&gt;
| HPF&lt;br /&gt;
| Front porch. The image is blanked during this period, and no pixels are pushed to the LCD.&lt;br /&gt;
&lt;br /&gt;
Unknown why, but a single dot of red is displayed before entering this mode.&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| HSync&lt;br /&gt;
| Triggers a HSync pulse.&lt;br /&gt;
&lt;br /&gt;
Based on behavior, this needs to last at least a pixel clock for the LCD to register the sync.&lt;br /&gt;
|-&lt;br /&gt;
| 0x14&lt;br /&gt;
| HPB&lt;br /&gt;
| Back porch? Has to be at least one bigger than HSync, otherwise HSync never triggers.&lt;br /&gt;
&lt;br /&gt;
The display is blank, and the LCD displays nothing in this period (doesn&#039;t push pixels).&lt;br /&gt;
|-&lt;br /&gt;
| 0x18&lt;br /&gt;
| HBL&lt;br /&gt;
| Left border trigger treshold. Enables pushing pixels to the display.&lt;br /&gt;
&lt;br /&gt;
If this value is smaller than the back porch, then the back porch period will be zero, and the border will be immediately displayed upon entering the back porch period.&lt;br /&gt;
&lt;br /&gt;
Can be lower than HSync, as the back porch is what takes the controller out of HSync.&lt;br /&gt;
&lt;br /&gt;
Must be &amp;lt;= HDisp start (reg 0x60 low u16), otherwise no pixels will be pushed due to a glitched state.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1C&lt;br /&gt;
| H Interrupt timing&lt;br /&gt;
| Made up from two u16 values, PDC interrupt line is asserted when HCount == low u16, and most likely deasserted when HCount == high u16.&lt;br /&gt;
&lt;br /&gt;
There seems to be some limitations though:&lt;br /&gt;
* low u16 must be smaller than high u16&lt;br /&gt;
* if low u16 is less than HTotal then high u16 must also be smaller than HTotal&lt;br /&gt;
* setting low u16 to &amp;gt;= HTotal disables the interrupt ever firing&lt;br /&gt;
&lt;br /&gt;
This is configured by gsp in a way so that low u16 equals to HTotal, meaning the HSync interrupt will never fire.&lt;br /&gt;
|-&lt;br /&gt;
| 0x20&lt;br /&gt;
| low u16: ???&lt;br /&gt;
high u16: ???&lt;br /&gt;
| ???&lt;br /&gt;
|-&lt;br /&gt;
| 0x24&lt;br /&gt;
| VTotal&lt;br /&gt;
| Total height of the timing window. Can be interpreted as the vertical clock divider.&lt;br /&gt;
&lt;br /&gt;
VClock = PClock / (HTotal + 1) / (VTotal + 1)&lt;br /&gt;
&lt;br /&gt;
Setting this to 494 lowers framerate to about 50.040660858 Hz ((268111856 / 24) / (450 + 1) / (494 + 1)).&lt;br /&gt;
|-&lt;br /&gt;
| 0x28&lt;br /&gt;
| ?&lt;br /&gt;
| Seems to determine the vertical blanking interval.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Setting this to lower than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will cut off the top &amp;lt;code&amp;gt;VTotal - VDisp - thisvalue&amp;lt;/code&amp;gt; lines.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will make the image be pushed downwards with the overscan color visible.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;HTotal&amp;lt;/code&amp;gt; will make the GPU skip vertical pixel data synchronization (hence filling the screen with the rest of the pixel data past the given screen framebuffer size). Also will skip &amp;lt;code&amp;gt;thisvalue + somevalue - HTotal&amp;lt;/code&amp;gt; lines into the &amp;quot;global&amp;quot; pixel buffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x30&lt;br /&gt;
| ?&lt;br /&gt;
| Total amount of vertical scanlines in the pixel buffer, must be bigger than *an unknown blanking-like value*. If this value is less than VDisp then the last two scanlines will be repeated interlaced until VDisp is reached.&lt;br /&gt;
|-&lt;br /&gt;
| 0x34&lt;br /&gt;
| VDisp(?)&lt;br /&gt;
| Total amonut of vertical scanlines displayed (only for top screen it seems like). If this value is less than VTotal then the rest of the scanlines will not be updated on the screen, so those will slowly fade out. Must be bigger than *an unknown blanking-like value*, otherwise an underflow will happen.&lt;br /&gt;
|-&lt;br /&gt;
| 0x38&lt;br /&gt;
| Vertical data offset(?)&lt;br /&gt;
| ??? Seems to offset the screen upwards if this value is high enough. If this value is higher or equal to *some value* (aka. if less than one scanline is displayed on the screen) then the screen will lose synchronization.&lt;br /&gt;
|-&lt;br /&gt;
| 0x40&lt;br /&gt;
| V Interrupt timing&lt;br /&gt;
| Similar to H Interrupt timing (0x1C), except the comparison is done against VCount, the limitations are emposed on VTotal, and the interrupt that fires is VSync.&lt;br /&gt;
&lt;br /&gt;
One important note is that it seems like the VSync interrupt always fires at HCount == 0, and there doesn&#039;t seem to be a register to control this behavior.&lt;br /&gt;
|-&lt;br /&gt;
| 0x44&lt;br /&gt;
| ???&lt;br /&gt;
| similar functionality to 0x10&lt;br /&gt;
|-&lt;br /&gt;
| 0x48&lt;br /&gt;
| ???&lt;br /&gt;
| bit0 seems to disable HSync, bit8 seems to disable VSync, rest of the bits aren&#039;t writable.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4C&lt;br /&gt;
| Overscan filler color&lt;br /&gt;
| 24bits(? top 8bits ignored)&lt;br /&gt;
&lt;br /&gt;
When the visible region is being drawn, but the timing parameters are set up in a way that the framebuffer is smaller than the visible region, it will be filled by this color.&lt;br /&gt;
|-&lt;br /&gt;
| 0x50&lt;br /&gt;
| HCount&lt;br /&gt;
| Horizontal &amp;quot;beam position&amp;quot; counter. Note that this value does not equal to the current pixel being drawn.&lt;br /&gt;
|-&lt;br /&gt;
| 0x54&lt;br /&gt;
| VCount&lt;br /&gt;
| Vertical &amp;quot;beam position&amp;quot; counter. Note that the scanline being drawn isn&#039;t equal to this value.&lt;br /&gt;
|-&lt;br /&gt;
| 0x5C&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: Image width (including some offset?)&lt;br /&gt;
high u16: Image height??? (seems to be unused)&lt;br /&gt;
|-&lt;br /&gt;
| 0x60&lt;br /&gt;
| HDisp&lt;br /&gt;
| low u16: Image start (border --&amp;gt; pixel data)&lt;br /&gt;
high u16: Image end (pixel data --&amp;gt; border)&lt;br /&gt;
|-&lt;br /&gt;
| 0x64&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: unknown&lt;br /&gt;
high u16: framebuffer total height (amount of scanlines blitted regardless of framebuffer height)&lt;br /&gt;
|-&lt;br /&gt;
| 0x68&lt;br /&gt;
| Framebuffer A first address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x6C&lt;br /&gt;
| Framebuffer A second address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x70&lt;br /&gt;
| Framebuffer format and other settings&lt;br /&gt;
| See [[#Framebuffer_format|framebuffer format]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x74&lt;br /&gt;
| PDC control&lt;br /&gt;
| Bit 0: Enable display controller.&lt;br /&gt;
Bit 8: HBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 9: VBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 10: Error IRQ mask? (0 = enabled).&lt;br /&gt;
Bit 16: Output enable?&lt;br /&gt;
|-&lt;br /&gt;
| 0x78&lt;br /&gt;
| Framebuffer select and status&lt;br /&gt;
| Bit 0: Next framebuffer to display (after VBlank).&lt;br /&gt;
Bit 4: Currently displaying framebuffer?&lt;br /&gt;
Bit 8: Reset FIFO?&lt;br /&gt;
Bit 16: HBlank IRQ status/ack. Write 1 to aknowledge.&lt;br /&gt;
Bit 17: VBlank IRQ status/ack.&lt;br /&gt;
Bit 18: Error IRQ status/ack?&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| Color lookup table index select&lt;br /&gt;
| 8bits, write-only&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| Color lookup table indexed element&lt;br /&gt;
| Contains the value of the color lookup table indexed by the above register, 24bits, RGB8 (0x00BBGGRR)  &lt;br /&gt;
Accessing this register will increase the index register by one&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| Framebuffer stride&lt;br /&gt;
| 32bits (bottom 3bits ignored?)&lt;br /&gt;
&lt;br /&gt;
Distance in bytes between the start of two framebuffer rows (must be a multiple of 8).&lt;br /&gt;
&lt;br /&gt;
In other words, this can be interpreted as the amount to add to the framebuffer pointer after displaying a scanline.&lt;br /&gt;
&lt;br /&gt;
Setting this to zero will cause only the first line of the image to be displayed repeated on the entire display. With the HSync interrupt it&#039;s possible to &amp;quot;race the beam&amp;quot; to (ab)use this feature.&lt;br /&gt;
&lt;br /&gt;
Because of this simplicity, writing a negative value here VFlips the image, although that requires the framebuffer pointer register to be set to the start of the last scanline, instead of at the start of the framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| Framebuffer B first address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| Framebuffer B second address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer format ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 2-0&lt;br /&gt;
| [[#Framebuffer_color_formats|Color format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5-4&lt;br /&gt;
| Framebuffer interlacing mode&lt;br /&gt;
&lt;br /&gt;
 0 - A  (no interlacing)&lt;br /&gt;
 1 - AA (scanline doubling)&lt;br /&gt;
 2 - AB (interlace enable)&lt;br /&gt;
 3 - BA (same as above, but the fields are inverted)&lt;br /&gt;
&lt;br /&gt;
In AB and BA interlace modes, a scanline from each framebuffer is output in an alternating manner. In AB mode, Framebuffer A is output on the frist display scanline. Similarly, in BA mode, Framebuffer B gets output to the first display scanline.&lt;br /&gt;
&lt;br /&gt;
The way AB and BA modes work, is that a scanline is output, the framebuffer stride value is added to the internal scanline pointer value, and the other framebuffer is selected. And this alternates until the end of the draw region.&lt;br /&gt;
&lt;br /&gt;
AA interlacing works like AB interlacing, except both internal framebuffer pointers are set to the Framebuffer A pointer value.&lt;br /&gt;
&lt;br /&gt;
In A mode (no interlacing), it doesn&#039;t switch to the other framebuffer at the end of outpuitting a scanline to the display.&lt;br /&gt;
&lt;br /&gt;
Bottom screen has this set to 0 (A mode, no interlacing) at all times.  &lt;br /&gt;
Top screen uses AB interlacing in 3D mode (with 3D slider enabled), and A mode (no interlacing) in 2D mode.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Alternative pixel output mode*&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 9-8&lt;br /&gt;
| DMA size&lt;br /&gt;
&lt;br /&gt;
 0 -  4 FCRAM words (32 bytes)&lt;br /&gt;
 1 -  8 FCRAM words (64 bytes)&lt;br /&gt;
 2 - 16 FCRAM words (128 bytes)&lt;br /&gt;
 3 - ???&lt;br /&gt;
&lt;br /&gt;
FCRAM doesn&#039;t support DMA size 3, as it can only burst up to 16 words (128 bytes), and will show a black screen instead.&lt;br /&gt;
|-&lt;br /&gt;
| 31-16&lt;br /&gt;
| Unknown&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt; The weird thing about bit6, is that it works different between the bottom and top LCD. On the bottom LCD, it doubles the number of outputted pixels (so the same pixel is outputted twice, effectively doing pixel/column doubling). However on the top screen, it does scanline doubling instead.  &lt;br /&gt;
Most likely the top screen receives two pixels at once per clock unit, outputting two scanlines simultaneously.&lt;br /&gt;
&lt;br /&gt;
On a 2DS, it seems to have no effect on the top part of the display, and on the bottom screen it just shifts the framebuffer to the right two pixels.&lt;br /&gt;
&lt;br /&gt;
GSP module only allows the LCD stereoscopy (3D) to be enabled when bit5=1 and bit6=0 here. When GSP module updates this register, GSP module will automatically disable the stereoscopy if those bits are not set for enabling stereoscopy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When both interlacing and alternative mode is disabled (bit6=0), the full resolution of the top screen (240x800) can be utilized if the PDC registers are updated to accomodate this higher resolution. GSP contains tables for this mode (gsp mode == 1). GSP automatically applies this mode if both bit5 and bit6 are cleared. This is also the default, and the only valid mode for the bottom screen in userland.&lt;br /&gt;
&lt;br /&gt;
If only AB interlacing is enabled (bit5=1, bit6=0), gsp detects this as a request to switch to 3D mode (gsp mode == 2), and enables the parallax barrier.  &lt;br /&gt;
It&#039;s unknown how to control this, but some other PDC registers control if interlacing should be done by true interleaving (both framebuffers are treated as 240x400), or by skipping lines (both framebuffers are treated as 240x800).&lt;br /&gt;
&lt;br /&gt;
If only alternative mode is enabled (bit5=0, bit6=1), gsp detects it as a request to switch back to 2D mode for the top screen (gsp mode == 0). This is also the default mode for the top screen.&lt;br /&gt;
&lt;br /&gt;
Both interlacing and scan doubling can&#039;t be enabled in usermode, but it works as expected in baremetal.&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer color formats ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Value&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| GL_RGBA8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| GL_RGB8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| GL_RGB565_OES&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| GL_RGB5_A1_OES&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| GL_RGBA4_OES&lt;br /&gt;
|}&lt;br /&gt;
Color components are laid out in reverse byte order, with the most significant bits used first (i.e. non-24-bit pixels are stored as a little-endian values). For instance, a raw data stream of two GL_RGB565_OES pixels looks like GGGBBBBB RRRRRGGG GGGBBBBB RRRRRGGG.&lt;br /&gt;
&lt;br /&gt;
Color formats 5, 6, and 7 are blocked by gsp, but they behave as pixel-doubled RGBA8 (not line doubling, but instead the same pixel is output twice) if used outside of userland.&lt;br /&gt;
&lt;br /&gt;
== Transfer Engine ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| Input physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C04&lt;br /&gt;
| Output physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C08&lt;br /&gt;
| DisplayTransfer output width (bits 0-15) and height (bits 16-31).&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C0C&lt;br /&gt;
| DisplayTransfer input width and height.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C10&lt;br /&gt;
| Transfer flags. (See below)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C14&lt;br /&gt;
| GSP module writes value 0 here prior to writing to 0x1EF00C18, for cmd3.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C18&lt;br /&gt;
|  Setting bit0 starts the transfer. Upon completion, bit0 is unset and bit8 is set.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C1C&lt;br /&gt;
|  ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C20&lt;br /&gt;
| TextureCopy total amount of data to copy, in bytes.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C24&lt;br /&gt;
| TextureCopy input line width (bits 0-15) and gap (bits 16-31), in 16 byte units.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C28&lt;br /&gt;
| TextureCopy output line width and gap.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP_Shared_Memory|GX command]] 3 and 4. For cmd4, *0x1EF00C18 |= 1 is used instead of just writing value 1. The DisplayTransfer registers are only used if bit 3 of the flags is unset and ignored otherwise. The TextureCopy registers are likewise only used if bit 3 is set, and ignored otherwise.&lt;br /&gt;
&lt;br /&gt;
The minimum supported dimension for output is 64x64, anything lower will hang the engine.&lt;br /&gt;
&lt;br /&gt;
==== Flags Register - 0x1EF00C10 ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| When set, the framebuffer data is flipped vertically.&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| When set, the input framebuffer is treated as linear and converted to tiled in the output, converts tiled-&amp;gt;linear when unset.&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| This bit is required when the output width is less than the input width for the hardware to properly crop the lines, otherwise the output will be mis-aligned.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Uses a TextureCopy mode transfer. See below for details.&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Don&#039;t perform tiled-linear conversion. Incompatible with bit 1, so only tiled-tiled transfers can be done, not linear-linear.&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 10-8&lt;br /&gt;
| Input framebuffer color format, value0 and value1 are the same as the [[GPU Registers#Framebuffer_color_formats|LCD Source Framebuffer Formats]] (usually zero)&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 14-12&lt;br /&gt;
| Output framebuffer color format&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| Use 32x32 block tiling mode, instead of the usual 8x8 one. Output dimensions must be multiples of 32, even if cropping with bit 2 set above.&lt;br /&gt;
|-&lt;br /&gt;
| 17-23&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 24-25&lt;br /&gt;
| Scale down the input image using a box filter. 0 = No downscale, 1 = 2x1 downscale. 2 = 2x2 downscale, 3 = invalid&lt;br /&gt;
|-&lt;br /&gt;
| 31-26&lt;br /&gt;
| Not writable&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== TextureCopy ===&lt;br /&gt;
&lt;br /&gt;
When bit 3 of the control register is set, the hardware performs a TextureCopy-mode transfer: no format conversions are done, instead a raw data copy is performed from the source to the destination, with a configurable gap between lines. All bits of the control register are ignored, except for input/output dimensions, which are used for line width and gap, and bit 2, which must be set when gaps are used.&lt;br /&gt;
&lt;br /&gt;
The total amount of bytes to copy is specified in the size register, the hardware loops reading lines from the input and writing them to the output until this amount is copied. The gap specifies the number of bytes to skip after each line read (a gap of 0 results in a contiguous read). Gaps do not count towards the total size of the transfer.&lt;br /&gt;
&lt;br /&gt;
When setting line width and gap they must be divided by 2 (it can be thought as the calculation being done in bits, and the values being stripped of their lower 4 bits for the alignment). For example, if the left half of a 32x32 RGB8 texture is to be copied, the parameters will be:&lt;br /&gt;
 line width = (16 * 24) &amp;gt;&amp;gt; 4 = 24&lt;br /&gt;
 gap = line width&lt;br /&gt;
 size = 16 * 32 * 3 = 1536&lt;br /&gt;
&lt;br /&gt;
By correctly calculating the input and output gap sizes it is possible to use this functionality to copy arbitrary sub-rectangles between differently-sized framebuffers or textures, which is one of its main uses over a regular no-conversion DisplayTransfer. When copying tiled textures/framebuffers it&#039;s important to remember that the contents of a tile are laid out sequentially in memory, and so this should be taken into account when calculating the transfer parameters.&lt;br /&gt;
&lt;br /&gt;
Specifying invalid/junk values for the TextureCopy dimensions can result in the GPU hanging while attempting to process this TextureCopy. For instance, when in contiguous mode the size must be at least 16; when in gap mode, the size must be at least 192, and the line width must not be 0.&lt;br /&gt;
&lt;br /&gt;
== Command List ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| Buffer size in bytes &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E8&lt;br /&gt;
| Buffer physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018F0&lt;br /&gt;
| Setting bit0 to 1 enables processing GPU command execution. Upon completion, bit0 seems to be reset to 0.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These 3 registers are used by [[GSP_Shared_Memory|GX command]] 1. This is used for [[GPU/Internal_Registers|GPU commands]].&lt;br /&gt;
&lt;br /&gt;
== Framebuffers ==&lt;br /&gt;
These LCD framebuffers normally contain the last rendered frames from the GPU. The framebuffers are drawn from left-to-right, instead of top-to-bottom.(Thus the beginning of the framebuffer is drawn starting at the left side of the screen)&lt;br /&gt;
&lt;br /&gt;
Both of the 3D screen left/right framebuffers are displayed regardless of the 3D slider&#039;s state, however when the 3D slider is set to &amp;quot;off&amp;quot; the 3D effect is disabled. Normally when the 3D slider&#039;s state is set to &amp;quot;off&amp;quot; the left/right framebuffer addresses are set to the same physical address. When the 3D effect is disabled and the left/right framebuffers are set to separate addresses, the LCD seems to alternate between displaying the left/right framebuffer each frame.&lt;br /&gt;
&lt;br /&gt;
==== Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00400 = 0x1C2&lt;br /&gt;
* 0x1EF00404 = 0xD1&lt;br /&gt;
* 0x1EF00408 = 0x1C1&lt;br /&gt;
* 0x1EF0040C = 0x1C1&lt;br /&gt;
* 0x1EF00410 = 0&lt;br /&gt;
* 0x1EF00414 = 0xCF&lt;br /&gt;
* 0x1EF00418 = 0xD1&lt;br /&gt;
* 0x1EF0041C = 0x1C501C1&lt;br /&gt;
* 0x1EF00420 = 0x10000&lt;br /&gt;
* 0x1EF00424 = 0x19D&lt;br /&gt;
* 0x1EF00428 = 2&lt;br /&gt;
* 0x1EF0042C = 0x1C2&lt;br /&gt;
* 0x1EF00430 = 0x1C2&lt;br /&gt;
* 0x1EF00434 = 0x1C2&lt;br /&gt;
* 0x1EF00438 = 1&lt;br /&gt;
* 0x1EF0043C = 2&lt;br /&gt;
* 0x1EF00440 = 0x1960192&lt;br /&gt;
* 0x1EF00444 = 0&lt;br /&gt;
* 0x1EF00448 = 0&lt;br /&gt;
* 0x1EF0045C = 0x19000F0&lt;br /&gt;
* 0x1EF00460 = 0x1c100d1&lt;br /&gt;
* 0x1EF00464 = 0x1920002&lt;br /&gt;
* 0x1EF00470 = 0x80340&lt;br /&gt;
* 0x1EF0049C = 0&lt;br /&gt;
&lt;br /&gt;
==== More Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00468 = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF0046C = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF00494 = 0x18300000&lt;br /&gt;
* 0x1EF00498 = 0x18300000&lt;br /&gt;
* 0x1EF00478 = 1, doesn&#039;t stay 1, read as 0&lt;br /&gt;
* 0x1EF00474 = 0x10501&lt;br /&gt;
&lt;br /&gt;
[[Category:GPU]]&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23488</id>
		<title>GSP Shared Memory</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23488"/>
		<updated>2025-05-12T12:54:34Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Done edits&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the structure of the GSP [[GSPGPU:RegisterInterruptRelayQueue|shared]] memory. Interrupt, framebuffer, and GX command data is stored here.&lt;br /&gt;
&lt;br /&gt;
=Interrupt Queue=&lt;br /&gt;
&lt;br /&gt;
The Interrupt queue is located at sharedMemBase + (clientID * 0x40).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| Offset from the count where to save incoming interrupts&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| Count (max 0x20 for PDC, 0x34 for others)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| Missed other interrupts (set to 1 when 0 and count &amp;gt;= 0x34)&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| Flags (bit0 = skip PDC)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4-0x7&lt;br /&gt;
| Missed PDC0 (incremented when flags.bit0 is clear and count &amp;gt;= 0x20) &lt;br /&gt;
|-&lt;br /&gt;
| 0x8-0xB&lt;br /&gt;
| Missed PDC1 (same as above)&lt;br /&gt;
|-&lt;br /&gt;
| 0xC-0x3F&lt;br /&gt;
| Interrupt list (u8) (0=PSC0, 1=PSC1, 2=PDC0/VBlankTop, 3=PDC1/VBlankBottom, 4=PPF, 5=P3D, 6=DMA)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP fills the interrupt list, then triggers the event set with [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]] for the specified process(es).&lt;br /&gt;
&lt;br /&gt;
PDC interrupts are sent to all processes; other interrupts are only sent to the process with rendering rights.&lt;br /&gt;
&lt;br /&gt;
GSP will only dispatch PSC0 if a [[GSP_Shared_Memory#Trigger_Memory_Fill|Memory Fill]] command has been issued with both buffers set.&lt;br /&gt;
&lt;br /&gt;
= Framebuffer Info =&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the top LCD is located at sharedMemBase + 0x200 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the bottom LCD is located at sharedMemBase + 0x240 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
|  Framebuffer info [[GSPGPU:SetBufferSwap|entry]] index&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Flags (bit0 = client has set new data)&lt;br /&gt;
|-&lt;br /&gt;
| 3-2&lt;br /&gt;
| Padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Structure ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Active framebuffer (0 = first, 1 = second)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Left framebuffer VA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Right framebuffer VA (top screen only)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Stride]] (offset 0x90)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| [[GPU/External_Registers#Framebuffer_format|Format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Status]] (offset 0x78)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ? (&amp;quot;Attribute&amp;quot;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a process sets this framebuffer info, it sets index to &amp;lt;nowiki&amp;gt;(index+1) &amp;amp; 1&amp;lt;/nowiki&amp;gt;. Then it writes the framebuffer info entry, and sets flag to value 1. The GSP module loads this framebuffer info entry data into GSP state once the [[GPU]] finishes processing GX commands 3 or 4. Once the GSP module finishes loading this framebuffer info, it sets flag to value 0, then it will not load the framebuffer info again until flag is value 1. After loading this entry data into GSP state, the GSP module then writes this framebuffer state to the [[LCD]] registers. GSP module automatically updates the LCD framebuffer registers each time GX commands 3 or 4 finish, even when this shared memory data was not updated by the application.(GSP module toggles the active framebuffer register when automatically updating LCD registers, when shared memory data is not used)&lt;br /&gt;
&lt;br /&gt;
The two 0x1C-byte framebuffer info entries are located at framebufferinfo+4.&lt;br /&gt;
&lt;br /&gt;
= 3D Slider and 3D [[GSPGPU:SetLedForceOff|LED]] =&lt;br /&gt;
&lt;br /&gt;
See [[Configuration Memory]].&lt;br /&gt;
&lt;br /&gt;
= Command Queue =&lt;br /&gt;
&lt;br /&gt;
The command queue is located at sharedMemBase + 0x800 + (clientID * 0x200). It consists of an header followed by at most 15 command entries. Each command entry is of size 0x20 and has an header followed by command specific parameters.&lt;br /&gt;
&lt;br /&gt;
After adding a command, [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]] must be used to start command processing (official code does so when the total commands field is 1).&lt;br /&gt;
&lt;br /&gt;
== Command Queue Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Index of the command to process, this is incremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Total commands to process, this is incremented by the application when adding the command to the queue, and decremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Status (bit0 = halted, bit7 = fatal error)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When bit0 is set, further processing of commands is halted until the client resets the flag and calls [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 7-4&lt;br /&gt;
| Result code for the last command which failed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP checks for status.bit0 and optionally avoids handling further commands, however the check is done by equality, which means it will always fail if status.bit7 is also set (and thus other commands will be processed). This bug prevents the halting logic from working propertly, but can be worked around by keeping bit0 of word3 set, as that will force halting on each iteration.&lt;br /&gt;
&lt;br /&gt;
== Command Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| When bit0 is set, GSP stops processing further commands (can be used for packing together sets of commands)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When set, the command fails if GSP is busy handling any other command; otherwise, it only fails if GSP is busy handling a command of the same kind&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Commands ==&lt;br /&gt;
&lt;br /&gt;
Addresses specified in parameters are virtual addresses. Depending on the command, there might be constraints on the accepted parameters. In general, some commands require parameters to be aligned, and addresses are expected to be on [[Memory_Management#Memory_Mapping|linear]], [[Memory_layout#0x1F000000_.28New_3DS_only.29|QTM]] or VRAM memory.&lt;br /&gt;
&lt;br /&gt;
=== Trigger DMA Request ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x00)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush source (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command issues a [[Corelink_DMA_Engines|DMA request]] as the process with [[GSPGPU:AcquireRight|rendering rights]]. When the destination address is within VRAM, GSP places itself as the destination process: this makes it possible to transfer data in VRAM without needing it listed in the destination process [[NCCH/Extended_Header#ARM11_Kernel_Capabilities|exheader mappings]]. Otherwise, both source and destination of the DMA request are the process with rendering rights.&lt;br /&gt;
&lt;br /&gt;
The source buffer must be mapped as readable in the source process, while the destination address must be mapped as writable in the destination process, otherwise GSP calls [[SVC|svcBreak]]. When flushing is enabled and the source address is above VRAM, svcFlushProcessDataCache is used to flush the source buffer.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the command does nothing.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Command List Processing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x01)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Update gas additive blend results (0 = don&#039;t update, 1 = update)&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush buffer (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Command_List|Command List registers]], and optionally updates gas additive blend results after command processing has ended.&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Address and size should be both aligned to 8 bytes, and the address should be in linear, QTM or VRAM memory, otherwise PA 0 is used. When flushing is enabled, [[SVC|svcFlushProcessDataCache]] is used to flush the buffer on the process that has acquired rendering rights.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the command does nothing.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Memory Fill ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x02)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer 0 start address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer 0 value&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buffer 0 end address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buffer 1 start address&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buffer 1 value&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buffer 1 end address&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Control0 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (Control1 &amp;lt;&amp;lt; 16)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Memory_Fill|Memory Fill registers]].&lt;br /&gt;
&lt;br /&gt;
Addresses should be aligned to 8 bytes and must be in linear, QTM or VRAM memory, otherwise error 0xE0E02BF5 (GSP_INVALID_ADDRESS) is returned. The start address for a buffer must be below its end address, else the same error is returned. If the start address for a buffer is 0, that buffer is skipped; otherwise, its relative PSC unit is used for the fill operation.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Display Transfer ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x03)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Source dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Output dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Flags&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Transfer_Engine|Display Transfer registers]].&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Addresses should be aligned to 8 bytes and should be in linear, QTM or VRAM memory, otherwise PA 0 is used.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Texture Copy ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x04)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Line width &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (gap &amp;lt;&amp;lt; 16)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Same as above, for the destination&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Flags&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#TextureCopy|Texture Copy registers]]. Note that GSP doesn&#039;t enforce bit3 of the flags to be set.&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Addresses and size should be aligned to 8 bytes, and the addresses should be in linear, QTM or VRAM memory, otherwise PA 0 is used.&lt;br /&gt;
&lt;br /&gt;
=== Flush Cache Regions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x05)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer 0 address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer 0 size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buffer 1 address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buffer 1 size&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buffer 2 address&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buffer 2 size&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command calls svcFlushProcessDataCache for each buffer on the process that has acquired rendering rights.&lt;br /&gt;
&lt;br /&gt;
If any call fails, its error is returned; If any buffer has size 0, the buffer is skipped. In both cases, subsequent buffers are not processed.&lt;br /&gt;
&lt;br /&gt;
Any process must have acquired rendering rights, otherwise the error 0xD8202A06 (GSP_NO_RIGHT) is returned.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23487</id>
		<title>GSP Shared Memory</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23487"/>
		<updated>2025-05-12T12:24:26Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: TransferEngine&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the structure of the GSP [[GSPGPU:RegisterInterruptRelayQueue|shared]] memory. Interrupt, framebuffer, and GX command data is stored here.&lt;br /&gt;
&lt;br /&gt;
=Interrupt Queue=&lt;br /&gt;
&lt;br /&gt;
The Interrupt queue is located at sharedMemBase + (clientID * 0x40).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| Offset from the count where to save incoming interrupts&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| Count (max 0x20 for PDC, 0x34 for others)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| Missed other interrupts (set to 1 when 0 and count &amp;gt;= 0x34)&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| Flags (bit0 = skip PDC)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4-0x7&lt;br /&gt;
| Missed PDC0 (incremented when flags.bit0 is clear and count &amp;gt;= 0x20) &lt;br /&gt;
|-&lt;br /&gt;
| 0x8-0xB&lt;br /&gt;
| Missed PDC1 (same as above)&lt;br /&gt;
|-&lt;br /&gt;
| 0xC-0x3F&lt;br /&gt;
| Interrupt list (u8) (0=PSC0, 1=PSC1, 2=PDC0/VBlankTop, 3=PDC1/VBlankBottom, 4=PPF, 5=P3D, 6=DMA)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP fills the interrupt list, then triggers the event set with [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]] for the specified process(es).&lt;br /&gt;
&lt;br /&gt;
PDC interrupts are sent to all processes; other interrupts are only sent to the process with GPU rights.&lt;br /&gt;
&lt;br /&gt;
= Framebuffer Info =&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the top LCD is located at sharedMemBase + 0x200 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the bottom LCD is located at sharedMemBase + 0x240 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
|  Framebuffer info [[GSPGPU:SetBufferSwap|entry]] index&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Flags (bit0 = client has set new data)&lt;br /&gt;
|-&lt;br /&gt;
| 3-2&lt;br /&gt;
| Padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Structure ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Active framebuffer (0 = first, 1 = second)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Left framebuffer VA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Right framebuffer VA (top screen only)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Stride]] (offset 0x90)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| [[GPU/External_Registers#Framebuffer_format|Format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Status]] (offset 0x78)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ? (&amp;quot;Attribute&amp;quot;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a process sets this framebuffer info, it sets index to &amp;lt;nowiki&amp;gt;(index+1) &amp;amp; 1&amp;lt;/nowiki&amp;gt;. Then it writes the framebuffer info entry, and sets flag to value 1. The GSP module loads this framebuffer info entry data into GSP state once the [[GPU]] finishes processing GX commands 3 or 4. Once the GSP module finishes loading this framebuffer info, it sets flag to value 0, then it will not load the framebuffer info again until flag is value 1. After loading this entry data into GSP state, the GSP module then writes this framebuffer state to the [[LCD]] registers. GSP module automatically updates the LCD framebuffer registers each time GX commands 3 or 4 finish, even when this shared memory data was not updated by the application.(GSP module toggles the active framebuffer register when automatically updating LCD registers, when shared memory data is not used)&lt;br /&gt;
&lt;br /&gt;
The two 0x1C-byte framebuffer info entries are located at framebufferinfo+4.&lt;br /&gt;
&lt;br /&gt;
= 3D Slider and 3D [[GSPGPU:SetLedForceOff|LED]] =&lt;br /&gt;
&lt;br /&gt;
See [[Configuration Memory]].&lt;br /&gt;
&lt;br /&gt;
= Command Queue =&lt;br /&gt;
&lt;br /&gt;
The command queue is located at sharedMemBase + 0x800 + (clientID * 0x200). It consists of an header followed by at most 15 command entries. Each command entry is of size 0x20 and has an header followed by command specific parameters.&lt;br /&gt;
&lt;br /&gt;
After adding a command, [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]] must be used to start command processing (official code does so when the total commands field is 1).&lt;br /&gt;
&lt;br /&gt;
== Command Queue Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Index of the command to process, this is incremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Total commands to process, this is incremented by the application when adding the command to the queue, and decremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Status (bit0 = halted, bit7 = fatal error)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When bit0 is set, further processing of commands is halted until the client resets the flag and calls [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 7-4&lt;br /&gt;
| Result code for the last command which failed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP checks for status.bit0 and optionally avoids handling further commands, however the check is done by equality, which means it will always fail if status.bit7 is also set (and thus other commands will be processed). This bug prevents the halting logic from working propertly, but can be worked around by keeping bit0 of word3 set, as that will force halting on each iteration.&lt;br /&gt;
&lt;br /&gt;
== Command Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| When bit0 is set, GSP stops processing further commands (can be used for packing together sets of commands)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When set, the command fails if GSP is busy handling any other command; otherwise, it only fails if GSP is busy handling a command of the same kind&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Commands ==&lt;br /&gt;
&lt;br /&gt;
Addresses specified in parameters are virtual addresses. Address and size parameters except for command 0 and command 5 must be 8-byte aligned.&lt;br /&gt;
&lt;br /&gt;
=== Trigger DMA Request ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x00)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush source (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command issues a [[Corelink_DMA_Engines|DMA request]] as the calling process. When the destination address is within VRAM, GSP places itself as the destination process, this makes it possible to transfer data in VRAM without needing it listed in the destination process [[NCCH/Extended_Header#ARM11_Kernel_Capabilities|exheader mappings]]. Otherwise, both source and destination of the DMA request are the calling process.&lt;br /&gt;
&lt;br /&gt;
The source buffer must be mapped as readable in the source process, while the destination address must be mapped as writable in the destination process. When flushing is enabled and the source address is above VRAM, [[SVC|svcFlushProcessDataCache]] is used to flush the source buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Command List Processing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x01)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Update gas additive blend results (0 = don&#039;t update, 1 = update)&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush buffer (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Command_List|Command List registers]], and optionally updates gas additive blend results after command processing has ended.&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Address and size should be both aligned to 8 bytes, and the address should be in linear, QTM or VRAM memory, otherwise PA 0 is used. When flushing is enabled, svcFlushProcessDataCache is used to flush the buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Memory Fill ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x02)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer 0 start address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer 0 value&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buffer 0 end address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buffer 1 start address&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buffer 1 value&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buffer 1 end address&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Control0 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (Control1 &amp;lt;&amp;lt; 16)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Memory_Fill|Memory Fill registers]].&lt;br /&gt;
&lt;br /&gt;
Addresses should be aligned to 8 bytes and must be in linear, QTM or VRAM memory, otherwise error 0xE0E02BF5 (GSP_INVALID_ADDRESS) is returned. The start address for a buffer must be less than its end address, else the same error is returned. If the start address for a buffer is 0, that buffer is skipped; otherwise, its relative PSC unit is used for the fill operation.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Display Transfer ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x03)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Source dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Output dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Flags&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Transfer_Engine|Display Transfer registers]].&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Addresses should be aligned to 8 bytes and should be in linear, QTM or VRAM memory, otherwise PA 0 is used.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Texture Copy ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x04)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Line width &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (gap &amp;lt;&amp;lt; 16)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Same as above, for the destination&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Flags&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#TextureCopy|Texture Copy registers]]. Note that GSP doesn&#039;t enforce bit3 of the flags to be set.&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Addresses and size should be aligned to 8 bytes, and the addresses should be in linear, QTM or VRAM memory, otherwise PA 0 is used.&lt;br /&gt;
&lt;br /&gt;
=== Flush Cache Regions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x05)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf1 address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 size&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf2 address&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf2 size&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The application buffer addresses specified in the parameters are used with [[SVC|svcFlushProcessDataCache]]. The input buf0 size must not be zero. When buf1 size is zero, svcFlushProcessDataCache() for buf1 and buf2 are skipped. When buf2 size is zero, svcFlushProcessDataCache() for buf2 is skipped.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23486</id>
		<title>GSP Shared Memory</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23486"/>
		<updated>2025-05-12T12:05:28Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: PSC + small edits&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the structure of the GSP [[GSPGPU:RegisterInterruptRelayQueue|shared]] memory. Interrupt, framebuffer, and GX command data is stored here.&lt;br /&gt;
&lt;br /&gt;
=Interrupt Queue=&lt;br /&gt;
&lt;br /&gt;
The Interrupt queue is located at sharedMemBase + (clientID * 0x40).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| Offset from the count where to save incoming interrupts&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| Count (max 0x20 for PDC, 0x34 for others)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| Missed other interrupts (set to 1 when 0 and count &amp;gt;= 0x34)&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| Flags (bit0 = skip PDC)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4-0x7&lt;br /&gt;
| Missed PDC0 (incremented when flags.bit0 is clear and count &amp;gt;= 0x20) &lt;br /&gt;
|-&lt;br /&gt;
| 0x8-0xB&lt;br /&gt;
| Missed PDC1 (same as above)&lt;br /&gt;
|-&lt;br /&gt;
| 0xC-0x3F&lt;br /&gt;
| Interrupt list (u8) (0=PSC0, 1=PSC1, 2=PDC0/VBlankTop, 3=PDC1/VBlankBottom, 4=PPF, 5=P3D, 6=DMA)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP fills the interrupt list, then triggers the event set with [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]] for the specified process(es).&lt;br /&gt;
&lt;br /&gt;
PDC interrupts are sent to all processes; other interrupts are only sent to the process with GPU rights.&lt;br /&gt;
&lt;br /&gt;
= Framebuffer Info =&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the top LCD is located at sharedMemBase + 0x200 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the bottom LCD is located at sharedMemBase + 0x240 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
|  Framebuffer info [[GSPGPU:SetBufferSwap|entry]] index&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Flags (bit0 = client has set new data)&lt;br /&gt;
|-&lt;br /&gt;
| 3-2&lt;br /&gt;
| Padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Structure ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Active framebuffer (0 = first, 1 = second)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Left framebuffer VA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Right framebuffer VA (top screen only)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Stride]] (offset 0x90)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| [[GPU/External_Registers#Framebuffer_format|Format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Status]] (offset 0x78)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ? (&amp;quot;Attribute&amp;quot;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a process sets this framebuffer info, it sets index to &amp;lt;nowiki&amp;gt;(index+1) &amp;amp; 1&amp;lt;/nowiki&amp;gt;. Then it writes the framebuffer info entry, and sets flag to value 1. The GSP module loads this framebuffer info entry data into GSP state once the [[GPU]] finishes processing GX commands 3 or 4. Once the GSP module finishes loading this framebuffer info, it sets flag to value 0, then it will not load the framebuffer info again until flag is value 1. After loading this entry data into GSP state, the GSP module then writes this framebuffer state to the [[LCD]] registers. GSP module automatically updates the LCD framebuffer registers each time GX commands 3 or 4 finish, even when this shared memory data was not updated by the application.(GSP module toggles the active framebuffer register when automatically updating LCD registers, when shared memory data is not used)&lt;br /&gt;
&lt;br /&gt;
The two 0x1C-byte framebuffer info entries are located at framebufferinfo+4.&lt;br /&gt;
&lt;br /&gt;
= 3D Slider and 3D [[GSPGPU:SetLedForceOff|LED]] =&lt;br /&gt;
&lt;br /&gt;
See [[Configuration Memory]].&lt;br /&gt;
&lt;br /&gt;
= Command Queue =&lt;br /&gt;
&lt;br /&gt;
The command queue is located at sharedMemBase + 0x800 + (clientID * 0x200). It consists of an header followed by at most 15 command entries. Each command entry is of size 0x20 and has an header followed by command specific parameters.&lt;br /&gt;
&lt;br /&gt;
After adding a command, [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]] must be used to start command processing (official code does so when the total commands field is 1).&lt;br /&gt;
&lt;br /&gt;
== Command Queue Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Index of the command to process, this is incremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Total commands to process, this is incremented by the application when adding the command to the queue, and decremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Status (bit0 = halted, bit7 = fatal error)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When bit0 is set, further processing of commands is halted until the client resets the flag and calls [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 7-4&lt;br /&gt;
| Result code for the last command which failed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP checks for status.bit0 and optionally avoids handling further commands, however the check is done by equality, which means it will always fail if status.bit7 is also set (and thus other commands will be processed). This bug prevents the halting logic from working propertly, but can be worked around by keeping bit0 of word3 set, as that will force halting on each iteration.&lt;br /&gt;
&lt;br /&gt;
== Command Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| When bit0 is set, GSP stops processing further commands (can be used for packing together sets of commands)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When set, the command fails if GSP is busy handling any other command; otherwise, it only fails if GSP is busy handling a command of the same kind&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Commands ==&lt;br /&gt;
&lt;br /&gt;
Addresses specified in parameters are virtual addresses. Address and size parameters except for command 0 and command 5 must be 8-byte aligned.&lt;br /&gt;
&lt;br /&gt;
=== Trigger DMA Request ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x00)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush source (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command issues a [[Corelink_DMA_Engines|DMA request]] as the calling process. When the destination address is within VRAM, GSP places itself as the destination process, this makes it possible to transfer data in VRAM without needing it listed in the destination process [[NCCH/Extended_Header#ARM11_Kernel_Capabilities|exheader mappings]]. Otherwise, both source and destination of the DMA request are the calling process.&lt;br /&gt;
&lt;br /&gt;
The source buffer must be mapped as readable in the source process, while the destination address must be mapped as writable in the destination process. When flushing is enabled and the source address is above VRAM, [[SVC|svcFlushProcessDataCache]] is used to flush the source buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Command List Processing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x01)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Update gas additive blend results (0 = don&#039;t update, 1 = update)&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush buffer (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the [[GPU/External_Registers#Command_List|Command List registers]], and optionally updates gas additive blend results after command processing has ended.&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. Address and size should be both aligned to 8 bytes, and the address should be in linear, QTM or VRAM memory, otherwise PA 0 is used. When flushing is enabled, svcFlushProcessDataCache is used to flush the buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Memory Fill ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x02)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer 0 start address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer 0 value&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buffer 0 end address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buffer 1 start address&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buffer 1 value&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buffer 1 end address&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Control0 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (Control1 &amp;lt;&amp;lt; 16)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the GPU [[GPU/External_Registers#Memory_Fill|Memory Fill registers]].&lt;br /&gt;
&lt;br /&gt;
Addresses should be aligned to 8 bytes and must be in linear, QTM or VRAM memory, otherwise error 0xE0E02BF5 (GSP_INVALID_ADDRESS) is returned. The start address for a buffer must be &amp;lt; its end address, else the same error is returned. If the start address for a buffer is 0, that buffer is skipped; otherwise, its relative PSC unit is used for the fill operation.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Display Transfer ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x03)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Input framebuffer [[GPU|dimensions]]&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Output framebuffer dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU|Flags]], for applications this is 0x1001000 for the main screen, and 0x1000 for the sub screen.&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these physical addresses and parameters to the [[GPU]] registers at 0x1EF00C00. This GPU command copies the already rendered framebuffer data from the input GPU framebuffer address to the specified output LCD framebuffer. The input framebuffer is normally located in VRAM.&lt;br /&gt;
&lt;br /&gt;
The GPU color buffer is stored in the same Z-curve (tiled) format as textures. By default, SetDisplayTransfer converts the given buffer from the tiled format to a linear format adapted to the LCD framebuffers.&lt;br /&gt;
&lt;br /&gt;
Display transfers are performed asynchronously, so after requesting a display transfer you should wait for the PPF interrupt to fire before reading the output data.&lt;br /&gt;
&lt;br /&gt;
The minimum supported dimension for output is 64x64, anything lower will hang the engine.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Texture Copy ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x04)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Total bytes to copy, not including gaps.&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Bits 0-15: Size of input line, in bytes. Bits 16-31: Gap between input lines, in bytes.&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Same as 4, but for the output.&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Flags, corresponding to the [[GPU/External_Registers#Transfer_Engine|Transfer Engine flags]]. However, for TextureCopy commands, bit 3 is always set, bit 2 is set if any output dimension is smaller than the input, and other bits are always 0.&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command is similar to cmd3. It also triggers the [[GPU/External_Registers#Transfer_Engine|GPU Transfer Engine]], but setting the TextureCopy parameters.&lt;br /&gt;
&lt;br /&gt;
=== Flush Cache Regions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x05)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf1 address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 size&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf2 address&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf2 size&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The application buffer addresses specified in the parameters are used with [[SVC|svcFlushProcessDataCache]]. The input buf0 size must not be zero. When buf1 size is zero, svcFlushProcessDataCache() for buf1 and buf2 are skipped. When buf2 size is zero, svcFlushProcessDataCache() for buf2 is skipped.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23483</id>
		<title>GSP Shared Memory</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23483"/>
		<updated>2025-05-12T06:30:22Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: P3D command&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the structure of the GSP [[GSPGPU:RegisterInterruptRelayQueue|shared]] memory. Interrupt, framebuffer, and GX command data is stored here.&lt;br /&gt;
&lt;br /&gt;
=Interrupt Queue=&lt;br /&gt;
&lt;br /&gt;
The Interrupt queue is located at sharedMemBase + (clientID * 0x40).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| Offset from the count where to save incoming interrupts&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| Count (max 0x20 for PDC, 0x34 for others)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| Missed other interrupts (set to 1 when 0 and count &amp;gt;= 0x34)&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| Flags (bit0 = skip PDC)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4-0x7&lt;br /&gt;
| Missed PDC0 (incremented when flags.bit0 is clear and count &amp;gt;= 0x20) &lt;br /&gt;
|-&lt;br /&gt;
| 0x8-0xB&lt;br /&gt;
| Missed PDC1 (same as above)&lt;br /&gt;
|-&lt;br /&gt;
| 0xC-0x3F&lt;br /&gt;
| Interrupt list (u8) (0=PSC0, 1=PSC1, 2=PDC0/VBlankTop, 3=PDC1/VBlankBottom, 4=PPF, 5=P3D, 6=DMA)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP fills the interrupt list, then triggers the event set with [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]] for the specified process(es).&lt;br /&gt;
&lt;br /&gt;
PDC interrupts are sent to all processes; other interrupts are only sent to the process with GPU rights.&lt;br /&gt;
&lt;br /&gt;
= Framebuffer Info =&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the top LCD is located at sharedMemBase + 0x200 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the bottom LCD is located at sharedMemBase + 0x240 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
|  Framebuffer info [[GSPGPU:SetBufferSwap|entry]] index&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Flags (bit0 = client has set new data)&lt;br /&gt;
|-&lt;br /&gt;
| 3-2&lt;br /&gt;
| Padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Structure ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Active framebuffer (0 = first, 1 = second)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Left framebuffer VA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Right framebuffer VA (top screen only)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Stride]] (offset 0x90)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| [[GPU/External_Registers#Framebuffer_format|Format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Status]] (offset 0x78)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ? (&amp;quot;Attribute&amp;quot;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a process sets this framebuffer info, it sets index to &amp;lt;nowiki&amp;gt;(index+1) &amp;amp; 1&amp;lt;/nowiki&amp;gt;. Then it writes the framebuffer info entry, and sets flag to value 1. The GSP module loads this framebuffer info entry data into GSP state once the [[GPU]] finishes processing GX commands 3 or 4. Once the GSP module finishes loading this framebuffer info, it sets flag to value 0, then it will not load the framebuffer info again until flag is value 1. After loading this entry data into GSP state, the GSP module then writes this framebuffer state to the [[LCD]] registers. GSP module automatically updates the LCD framebuffer registers each time GX commands 3 or 4 finish, even when this shared memory data was not updated by the application.(GSP module toggles the active framebuffer register when automatically updating LCD registers, when shared memory data is not used)&lt;br /&gt;
&lt;br /&gt;
The two 0x1C-byte framebuffer info entries are located at framebufferinfo+4.&lt;br /&gt;
&lt;br /&gt;
= 3D Slider and 3D [[GSPGPU:SetLedForceOff|LED]] =&lt;br /&gt;
&lt;br /&gt;
See [[Configuration Memory]].&lt;br /&gt;
&lt;br /&gt;
= Command Queue =&lt;br /&gt;
&lt;br /&gt;
The command queue is located at sharedMemBase + 0x800 + (clientID * 0x200). It consists of an header followed by at most 15 command entries. Each command entry is of size 0x20 and has an header followed by command specific parameters.&lt;br /&gt;
&lt;br /&gt;
After adding a command, [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]] must be used to start command processing (official code does so when the total commands field is 1).&lt;br /&gt;
&lt;br /&gt;
== Command Queue Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Index of the command to process, this is incremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Total commands to process, this is incremented by the application when adding the command to the queue, and decremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Status (bit0 = halted, bit7 = fatal error)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When bit0 is set, further processing of commands is halted until the client resets the flag and calls [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 7-4&lt;br /&gt;
| Result code for the last command which failed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP checks for status.bit0 and optionally avoids handling further commands, however the check is done by equality, which means it will always fail if status.bit7 is also set (and thus other commands will be processed). This bug prevents the halting logic from working propertly, but can be worked around by keeping bit0 of word3 set, as that will force halting on each iteration.&lt;br /&gt;
&lt;br /&gt;
== Command Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| When bit0 is set, GSP stops processing further commands (can be used for packing together sets of commands)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When set, the command fails if GSP is busy handling any other command; otherwise, it only fails if GSP is busy handling a command of the same kind&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Commands ==&lt;br /&gt;
&lt;br /&gt;
Addresses specified in parameters are virtual addresses. Address and size parameters except for command 0 and command 5 must be 8-byte aligned.&lt;br /&gt;
&lt;br /&gt;
=== Trigger DMA Request ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x00)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush source (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command issues a [[Corelink_DMA_Engines|DMA request]] as the calling process. When the destination address is within VRAM, GSP places itself as the destination process, this makes it possible to transfer data in VRAM without needing it listed in the destination process [[NCCH/Extended_Header#ARM11_Kernel_Capabilities|exheader mappings]]. Otherwise, both source and destination of the DMA request are the calling process.&lt;br /&gt;
&lt;br /&gt;
The source buffer must be mapped as readable in the source process, while the destination address must be mapped as writable in the destination process. When flushing is enabled and the source address is above VRAM, [[SVC|svcFlushProcessDataCache]] is used to flush the source buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Command List Processing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x01)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Update gas additive blend results (0 = don&#039;t update, 1 = update)&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush buffer (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command sets the GPU [[GPU/External_Registers#Command_List|command list registers]], and optionally updates gas additive blend results after command processing has ended.&lt;br /&gt;
&lt;br /&gt;
No error checking is performed on the parameters. address and size should be both 8-byte aligned, and the address should be in linear, QTM or VRAM memory, otherwise PA 0 is returned by the vaddr-&amp;gt;paddr conversion code. When flushing is enabled, svcFlushProcessDataCache is used to flush the buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Memory Fill ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x02)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 start address (0 = don&#039;t fill anything)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 value&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf0 end address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 start address (0 = don&#039;t fill anything)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf1 value&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf1 end address&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Control0 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (Control1 &amp;lt;&amp;lt; 16)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these addresses and the specified parameters to the [[GPU]] registers at 0x1EF00010 and 0x1EF00020. Doing so fills the specified buffers with the associated 4-byte value. This is used to clear GPU framebuffers.&lt;br /&gt;
The associated buffer address must not be &amp;lt;= to the main buffer address, thus the associated buffer address must not be zero as well. When the bufX address is zero, processing for the bufX parameters is skipped.&lt;br /&gt;
&lt;br /&gt;
The values of Control0 and Control1 give information about the type of memory fill. See [[GPU/External_Registers#Memory Fill|here]] for more information about memory fill parameters.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Display Transfer ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x03)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Input framebuffer [[GPU|dimensions]]&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Output framebuffer dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU|Flags]], for applications this is 0x1001000 for the main screen, and 0x1000 for the sub screen.&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these physical addresses and parameters to the [[GPU]] registers at 0x1EF00C00. This GPU command copies the already rendered framebuffer data from the input GPU framebuffer address to the specified output LCD framebuffer. The input framebuffer is normally located in VRAM.&lt;br /&gt;
&lt;br /&gt;
The GPU color buffer is stored in the same Z-curve (tiled) format as textures. By default, SetDisplayTransfer converts the given buffer from the tiled format to a linear format adapted to the LCD framebuffers.&lt;br /&gt;
&lt;br /&gt;
Display transfers are performed asynchronously, so after requesting a display transfer you should wait for the PPF interrupt to fire before reading the output data.&lt;br /&gt;
&lt;br /&gt;
The minimum supported dimension for output is 64x64, anything lower will hang the engine.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Texture Copy ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x04)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Total bytes to copy, not including gaps.&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Bits 0-15: Size of input line, in bytes. Bits 16-31: Gap between input lines, in bytes.&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Same as 4, but for the output.&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Flags, corresponding to the [[GPU/External_Registers#Transfer_Engine|Transfer Engine flags]]. However, for TextureCopy commands, bit 3 is always set, bit 2 is set if any output dimension is smaller than the input, and other bits are always 0.&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command is similar to cmd3. It also triggers the [[GPU/External_Registers#Transfer_Engine|GPU Transfer Engine]], but setting the TextureCopy parameters.&lt;br /&gt;
&lt;br /&gt;
=== Flush Cache Regions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x05)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf1 address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 size&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf2 address&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf2 size&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The application buffer addresses specified in the parameters are used with [[SVC|svcFlushProcessDataCache]]. The input buf0 size must not be zero. When buf1 size is zero, svcFlushProcessDataCache() for buf1 and buf2 are skipped. When buf2 size is zero, svcFlushProcessDataCache() for buf2 is skipped.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23482</id>
		<title>GSP Shared Memory</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23482"/>
		<updated>2025-05-12T06:03:11Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Elaborate on GX command 0&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the structure of the GSP [[GSPGPU:RegisterInterruptRelayQueue|shared]] memory. Interrupt, framebuffer, and GX command data is stored here.&lt;br /&gt;
&lt;br /&gt;
=Interrupt Queue=&lt;br /&gt;
&lt;br /&gt;
The Interrupt queue is located at sharedMemBase + (clientID * 0x40).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| Offset from the count where to save incoming interrupts&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| Count (max 0x20 for PDC, 0x34 for others)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| Missed other interrupts (set to 1 when 0 and count &amp;gt;= 0x34)&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| Flags (bit0 = skip PDC)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4-0x7&lt;br /&gt;
| Missed PDC0 (incremented when flags.bit0 is clear and count &amp;gt;= 0x20) &lt;br /&gt;
|-&lt;br /&gt;
| 0x8-0xB&lt;br /&gt;
| Missed PDC1 (same as above)&lt;br /&gt;
|-&lt;br /&gt;
| 0xC-0x3F&lt;br /&gt;
| Interrupt list (u8) (0=PSC0, 1=PSC1, 2=PDC0/VBlankTop, 3=PDC1/VBlankBottom, 4=PPF, 5=P3D, 6=DMA)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP fills the interrupt list, then triggers the event set with [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]] for the specified process(es).&lt;br /&gt;
&lt;br /&gt;
PDC interrupts are sent to all processes; other interrupts are only sent to the process with GPU rights.&lt;br /&gt;
&lt;br /&gt;
= Framebuffer Info =&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the top LCD is located at sharedMemBase + 0x200 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the bottom LCD is located at sharedMemBase + 0x240 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
|  Framebuffer info [[GSPGPU:SetBufferSwap|entry]] index&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Flags (bit0 = client has set new data)&lt;br /&gt;
|-&lt;br /&gt;
| 3-2&lt;br /&gt;
| Padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Structure ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Active framebuffer (0 = first, 1 = second)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Left framebuffer VA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Right framebuffer VA (top screen only)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Stride]] (offset 0x90)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| [[GPU/External_Registers#Framebuffer_format|Format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Status]] (offset 0x78)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ? (&amp;quot;Attribute&amp;quot;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a process sets this framebuffer info, it sets index to &amp;lt;nowiki&amp;gt;(index+1) &amp;amp; 1&amp;lt;/nowiki&amp;gt;. Then it writes the framebuffer info entry, and sets flag to value 1. The GSP module loads this framebuffer info entry data into GSP state once the [[GPU]] finishes processing GX commands 3 or 4. Once the GSP module finishes loading this framebuffer info, it sets flag to value 0, then it will not load the framebuffer info again until flag is value 1. After loading this entry data into GSP state, the GSP module then writes this framebuffer state to the [[LCD]] registers. GSP module automatically updates the LCD framebuffer registers each time GX commands 3 or 4 finish, even when this shared memory data was not updated by the application.(GSP module toggles the active framebuffer register when automatically updating LCD registers, when shared memory data is not used)&lt;br /&gt;
&lt;br /&gt;
The two 0x1C-byte framebuffer info entries are located at framebufferinfo+4.&lt;br /&gt;
&lt;br /&gt;
= 3D Slider and 3D [[GSPGPU:SetLedForceOff|LED]] =&lt;br /&gt;
&lt;br /&gt;
See [[Configuration Memory]].&lt;br /&gt;
&lt;br /&gt;
= Command Queue =&lt;br /&gt;
&lt;br /&gt;
The command queue is located at sharedMemBase + 0x800 + (clientID * 0x200). It consists of an header followed by at most 15 command entries. Each command entry is of size 0x20 and has an header followed by command specific parameters.&lt;br /&gt;
&lt;br /&gt;
After adding a command, [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]] must be used to start command processing (official code does so when the total commands field is 1).&lt;br /&gt;
&lt;br /&gt;
== Command Queue Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Index of the command to process, this is incremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Total commands to process, this is incremented by the application when adding the command to the queue, and decremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Status (bit0 = halted, bit7 = fatal error)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When bit0 is set, further processing of commands is halted until the client resets the flag and calls [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 7-4&lt;br /&gt;
| Result code for the last command which failed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP checks for status.bit0 and optionally avoids handling further commands, however the check is done by equality, which means it will always fail if status.bit7 is also set (and thus other commands will be processed). This bug prevents the halting logic from working propertly, but can be worked around by keeping bit0 of word3 set, as that will force halting on each iteration.&lt;br /&gt;
&lt;br /&gt;
== Command Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| When bit0 is set, GSP stops processing further commands (can be used for packing together sets of commands)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When set, the command fails if GSP is busy handling any other command; otherwise, it only fails if GSP is busy handling a command of the same kind&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Commands ==&lt;br /&gt;
&lt;br /&gt;
Addresses specified in parameters are virtual addresses. Address and size parameters except for command 0 and command 5 must be 8-byte aligned.&lt;br /&gt;
&lt;br /&gt;
=== Trigger DMA Request ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x00)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush source (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command issues a [[Corelink_DMA_Engines|DMA request]] as the calling process. When the destination address is within VRAM, GSP places itself as the destination process, this makes it possible to transfer data in VRAM without needing it listed in the destination process [[NCCH/Extended_Header#ARM11_Kernel_Capabilities|exheader mappings]]. Otherwise, both source and destination of the DMA request are the calling process.&lt;br /&gt;
&lt;br /&gt;
The source buffer must be mapped as readable in the source process, while the destination address must be mapped as writable in the destination process. When flushing is enabled and the source address is above VRAM, [[SVC|svcFlushProcessDataCache]] is used to flush the source buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Command List Processing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x01)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Update gas additive blend results (0 = don&#039;t update, 1 = update)&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush buffer (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified address to a physical address, then writes the physical address and size to the [[GPU]] registers at 0x1EF018E0. This buffer contains [[GPU/Internal_Registers|GPU commands]]. When flushing is enabled, svcFlushProcessDataCache is used to flush the buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Memory Fill ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x02)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 start address (0 = don&#039;t fill anything)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 value&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf0 end address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 start address (0 = don&#039;t fill anything)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf1 value&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf1 end address&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Control0 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (Control1 &amp;lt;&amp;lt; 16)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these addresses and the specified parameters to the [[GPU]] registers at 0x1EF00010 and 0x1EF00020. Doing so fills the specified buffers with the associated 4-byte value. This is used to clear GPU framebuffers.&lt;br /&gt;
The associated buffer address must not be &amp;lt;= to the main buffer address, thus the associated buffer address must not be zero as well. When the bufX address is zero, processing for the bufX parameters is skipped.&lt;br /&gt;
&lt;br /&gt;
The values of Control0 and Control1 give information about the type of memory fill. See [[GPU/External_Registers#Memory Fill|here]] for more information about memory fill parameters.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Display Transfer ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x03)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Input framebuffer [[GPU|dimensions]]&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Output framebuffer dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU|Flags]], for applications this is 0x1001000 for the main screen, and 0x1000 for the sub screen.&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these physical addresses and parameters to the [[GPU]] registers at 0x1EF00C00. This GPU command copies the already rendered framebuffer data from the input GPU framebuffer address to the specified output LCD framebuffer. The input framebuffer is normally located in VRAM.&lt;br /&gt;
&lt;br /&gt;
The GPU color buffer is stored in the same Z-curve (tiled) format as textures. By default, SetDisplayTransfer converts the given buffer from the tiled format to a linear format adapted to the LCD framebuffers.&lt;br /&gt;
&lt;br /&gt;
Display transfers are performed asynchronously, so after requesting a display transfer you should wait for the PPF interrupt to fire before reading the output data.&lt;br /&gt;
&lt;br /&gt;
The minimum supported dimension for output is 64x64, anything lower will hang the engine.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Texture Copy ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x04)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Total bytes to copy, not including gaps.&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Bits 0-15: Size of input line, in bytes. Bits 16-31: Gap between input lines, in bytes.&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Same as 4, but for the output.&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Flags, corresponding to the [[GPU/External_Registers#Transfer_Engine|Transfer Engine flags]]. However, for TextureCopy commands, bit 3 is always set, bit 2 is set if any output dimension is smaller than the input, and other bits are always 0.&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command is similar to cmd3. It also triggers the [[GPU/External_Registers#Transfer_Engine|GPU Transfer Engine]], but setting the TextureCopy parameters.&lt;br /&gt;
&lt;br /&gt;
=== Flush Cache Regions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x05)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf1 address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 size&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf2 address&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf2 size&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The application buffer addresses specified in the parameters are used with [[SVC|svcFlushProcessDataCache]]. The input buf0 size must not be zero. When buf1 size is zero, svcFlushProcessDataCache() for buf1 and buf2 are skipped. When buf2 size is zero, svcFlushProcessDataCache() for buf2 is skipped.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=IO_Registers&amp;diff=23481</id>
		<title>IO Registers</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=IO_Registers&amp;diff=23481"/>
		<updated>2025-05-10T07:07:03Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Overview =&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Old3DS&lt;br /&gt;
! A9/A11&lt;br /&gt;
! Category&lt;br /&gt;
! Physaddr&lt;br /&gt;
! Used by&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[CONFIG9 Registers]]&lt;br /&gt;
| 0x10000000&lt;br /&gt;
| Boot9, Process9&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[IRQ Registers]]&lt;br /&gt;
| 0x10001000&lt;br /&gt;
| Boot9, Process9, Kernel9&lt;br /&gt;
| ARM9 Interrupt Masking&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[NDMA Registers]]&lt;br /&gt;
| 0x10002000&lt;br /&gt;
| Boot9, Process9&lt;br /&gt;
| AHB DMA Engine&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[TIMER Registers]]&lt;br /&gt;
| 0x10003000&lt;br /&gt;
| Boot9, Process9&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[CTRCARD Registers]]&lt;br /&gt;
| 0x10004000 / 0x10005000&lt;br /&gt;
| Process9&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[EMMC Registers]]&lt;br /&gt;
| 0x10006000 / 0x10007000&lt;br /&gt;
| Boot9, Process9, NewKernel9Loader&lt;br /&gt;
| SD(IO) controller 1 and 3. 3 is normally mapped to ARM11.&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[PXI Registers]]&lt;br /&gt;
| 0x10008000&lt;br /&gt;
| Boot9, Process9&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[AES Registers]]&lt;br /&gt;
| 0x10009000&lt;br /&gt;
| Boot9, Process9, NewKernel9Loader&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[SHA Registers]]&lt;br /&gt;
| 0x1000A000&lt;br /&gt;
| Boot9, Process9, NewKernel9Loader&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[RSA Registers]]&lt;br /&gt;
| 0x1000B000&lt;br /&gt;
| Boot9, Process9&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[Corelink DMA Engines|XDMA Registers]]&lt;br /&gt;
| 0x1000C000&lt;br /&gt;
| Boot9, Kernel9&lt;br /&gt;
| [http://infocenter.arm.com/help/topic/com.arm.doc.subset.primecell.system/index.html CoreLink™ DMA-330 r0p0] (AXI busmaster, two channels, uses 32-bit bus width instead of 64).&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[SPICARD Registers]]&lt;br /&gt;
| 0x1000D800&lt;br /&gt;
| Process9&lt;br /&gt;
|&lt;br /&gt;
|-style=&amp;quot;border-top: double&amp;quot;&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[CONFIG Registers]]&lt;br /&gt;
| 0x10010000&lt;br /&gt;
| Process9&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| PRNG Registers&lt;br /&gt;
| 0x10011000&lt;br /&gt;
| Boot9, Process9&lt;br /&gt;
| Used as entropy-source for seeding random number generators.&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[OTP Registers]]&lt;br /&gt;
| 0x10012000&lt;br /&gt;
| Boot9, Kernel9, NewKernel9Loader&lt;br /&gt;
| Top secret.&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A9&lt;br /&gt;
| [[ARM7|ARM7 Registers]]&lt;br /&gt;
| 0x10018000&lt;br /&gt;
| TwlProcess9&lt;br /&gt;
| Used to setup the ARM7 core for AGB/TWL&lt;br /&gt;
|-style=&amp;quot;border-top: double&amp;quot;&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| TMIO SD(IO) controller 3&lt;br /&gt;
| 0x10100000&lt;br /&gt;
| &lt;br /&gt;
| NWM references this controller but doesn&#039;t have access to it.&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[HASH Registers]]&lt;br /&gt;
| 0x10101000&lt;br /&gt;
| [[Filesystem services]]&lt;br /&gt;
| These registers function the same as the [[SHA Registers]], with the exception of the FIFO being located at 0x10301000.&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[Y2R Registers]]&lt;br /&gt;
| 0x10102000&lt;br /&gt;
| [[Camera Services]]&lt;br /&gt;
| y2r&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[CSND Registers]]&lt;br /&gt;
| 0x10103000&lt;br /&gt;
| TwlBg, [[Codec Services]], [[CSND Services]], [[DSP Services]]&lt;br /&gt;
| Sound hardware.&lt;br /&gt;
|-style=&amp;quot;border-top: double&amp;quot;&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[MTX_Registers|LgyFb bottom screen]]&lt;br /&gt;
| 0x10110000&lt;br /&gt;
| TwlBg&lt;br /&gt;
| IO registers used to access legacy output framebuffer, as well as configure the upscaling filter.&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[MTX_Registers|LgyFb top screen]]&lt;br /&gt;
| 0x10111000&lt;br /&gt;
| TwlBg&lt;br /&gt;
| IO registers used to access legacy output framebuffer, as well as configure the upscaling filter.&lt;br /&gt;
|-style=&amp;quot;border-top: double&amp;quot;&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[Camera Registers]] &lt;br /&gt;
| 0x10120000&lt;br /&gt;
| [[Camera Services]]&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[Camera Registers]]&lt;br /&gt;
| 0x10121000&lt;br /&gt;
| [[Camera Services]]&lt;br /&gt;
| Mirror of 0x10120000?&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[WIFI Registers]]&lt;br /&gt;
| 0x10122000&lt;br /&gt;
| [[NWM Services]]&lt;br /&gt;
| WIFI SDIO bus registers&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| ?&lt;br /&gt;
| 0x10123000&lt;br /&gt;
| [[NWM Services]]&lt;br /&gt;
| WIFI?&lt;br /&gt;
|-style=&amp;quot;border-top: double&amp;quot;&lt;br /&gt;
| style=&amp;quot;background: red&amp;quot; | No&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[MVD Registers]]&lt;br /&gt;
| 0x10130000&lt;br /&gt;
| [[MVD Services]]&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: red&amp;quot; | No&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[MVD Registers]]&lt;br /&gt;
| 0x10131000&lt;br /&gt;
| [[MVD Services]]&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: red&amp;quot; | No&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[MVD Registers]]&lt;br /&gt;
| 0x10132000&lt;br /&gt;
| [[MVD Services]]&lt;br /&gt;
| &lt;br /&gt;
|-style=&amp;quot;border-top: double&amp;quot;&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[CONFIG11 Registers]]&lt;br /&gt;
| 0x10140000&lt;br /&gt;
| Process9, Boot11, Kernel11, TwlBg, [[DSP Services]], [[SPI Services]], [[NWM Services]] (until [[11.4.0-37#NWM-sysmodule|11.4.X]])&lt;br /&gt;
| System configuration. &lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[PDN Registers]]&lt;br /&gt;
| 0x10141000&lt;br /&gt;
| Process9, Boot11, Kernel11, TwlBg, [[Codec Services]], [[NWM Services]], [[SPI Services]], [[PDN Services]]&lt;br /&gt;
| Power management&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[SPI Registers]]&lt;br /&gt;
| 0x10142000&lt;br /&gt;
| TwlBg, [[SPI Services]]&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[SPI Registers]]&lt;br /&gt;
| 0x10143000&lt;br /&gt;
| TwlBg, dmnt Module&lt;br /&gt;
| Debugger related?&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[I2C Registers]]&lt;br /&gt;
| 0x10144000&lt;br /&gt;
| Boot11, Kernel11, TwlBg, [[I2C Services]]&lt;br /&gt;
| 3DS I2C interface (MCU + Cameras + LCD)&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[I2S Registers]]&lt;br /&gt;
| 0x10145000&lt;br /&gt;
| TwlBg, AgbBg, [[Codec Services]]&lt;br /&gt;
| Sound input/output lines&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[HID Registers]]&lt;br /&gt;
| 0x10146000&lt;br /&gt;
| Boot9, Boot11, Kernel11, TwlBg, [[HID Services]], dlp Services&lt;br /&gt;
| See [[PAD]].&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[GPIO Registers]]&lt;br /&gt;
| 0x10147000&lt;br /&gt;
| Boot11, TwlBg, [[GPIO Services]], [[DSP Services]](v0)&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[I2C Registers]]&lt;br /&gt;
| 0x10148000&lt;br /&gt;
| TwlBg, [[I2C Services]]&lt;br /&gt;
| 3DS I2C interface (Gyro + IR)&lt;br /&gt;
|-style=&amp;quot;border-top: double&amp;quot;&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[SPI Registers]]&lt;br /&gt;
| 0x10160000&lt;br /&gt;
| Boot9, TwlBg, [[SPI Services]]&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[I2C Registers]]&lt;br /&gt;
| 0x10161000&lt;br /&gt;
| Boot11, TwlBg, [[I2C Services]]&lt;br /&gt;
| TWL I2C interface (MCU + Cameras)&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[MIC Registers]]&lt;br /&gt;
| 0x10162000&lt;br /&gt;
| [[MIC Services]]&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[PXI Registers]]&lt;br /&gt;
| 0x10163000&lt;br /&gt;
| Boot11, Kernel11, TwlBg, [[PXI Services]]&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[NTRCARD Registers]]&lt;br /&gt;
| 0x10164000&lt;br /&gt;
| Boot9, Process9&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[MP Registers]]&lt;br /&gt;
| 0x10165000&lt;br /&gt;
| [[MP Services]]&lt;br /&gt;
|&lt;br /&gt;
|-style=&amp;quot;border-top: double&amp;quot;&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
|  [[MP Registers]]&lt;br /&gt;
| 0x10170000&lt;br /&gt;
| [[MP Services]]&lt;br /&gt;
| NTR WIFI Registers, see [http://problemkaputt.de/gbatek.htm#dswirelesscommunications GBATek].&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
|  [[MP Registers]]&lt;br /&gt;
| 0x10171000&lt;br /&gt;
| [[MP Services]]&lt;br /&gt;
| NTR WIFI Registers (mirror)&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
|?&lt;br /&gt;
| 0x10172000&lt;br /&gt;
|?&lt;br /&gt;
| NTR WIFI Unused?&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
|?&lt;br /&gt;
| 0x10173000&lt;br /&gt;
|?&lt;br /&gt;
| NTR WIFI Unused?&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[MP Registers]]&lt;br /&gt;
| 0x10174000&lt;br /&gt;
| [[MP Services]]&lt;br /&gt;
| NTR WIFI RAM&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[MP Registers]]&lt;br /&gt;
| 0x10175000&lt;br /&gt;
|?&lt;br /&gt;
| NTR WIFI RAM&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
|  [[MP Registers]]&lt;br /&gt;
| 0x10176000&lt;br /&gt;
|?&lt;br /&gt;
| NTR WIFI Registers (mirror)&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
|  [[MP Registers]]&lt;br /&gt;
| 0x10177000&lt;br /&gt;
|?&lt;br /&gt;
| NTR WIFI Registers (mirror)&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11/A9&lt;br /&gt;
| [[MP Registers]]&lt;br /&gt;
| 0x10178000 - 0x10180000&lt;br /&gt;
| [[MP Services]]&lt;br /&gt;
| NTR WIFI WS1 Region&lt;br /&gt;
|-style=&amp;quot;border-top: double&amp;quot;&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11&lt;br /&gt;
| [[Corelink DMA Engines|CDMA]]&lt;br /&gt;
| 0x10200000&lt;br /&gt;
| Boot11, Kernel11&lt;br /&gt;
| [http://infocenter.arm.com/help/topic/com.arm.doc.subset.primecell.system/index.html CoreLink™ DMA-330 r0p0] (eight channels). Only used by bootrom on New3DS.&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11&lt;br /&gt;
| FCRAM configuration&lt;br /&gt;
| 0x10201000&lt;br /&gt;
| TwlBg, Kernel11 (dead code)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11&lt;br /&gt;
| [[LCD Registers]]&lt;br /&gt;
| 0x10202000&lt;br /&gt;
| TwlBg, Kernel11, [[GSP Services]]&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11&lt;br /&gt;
| [[DSP Registers]]&lt;br /&gt;
| 0x10203000&lt;br /&gt;
| [[DSP Services]]&lt;br /&gt;
| see the &amp;quot;DSi XpertTeak&amp;quot; section in [http://problemkaputt.de/gba.htm no$gba] help.&lt;br /&gt;
|-style=&amp;quot;border-top: double&amp;quot;&lt;br /&gt;
| style=&amp;quot;background: red&amp;quot; | No&lt;br /&gt;
| A11&lt;br /&gt;
| ?&lt;br /&gt;
| 0x10204000&lt;br /&gt;
| ?&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
|  style=&amp;quot;background: red&amp;quot; | No&lt;br /&gt;
| A11&lt;br /&gt;
| [[Corelink DMA Engines|CDMA]]&lt;br /&gt;
| 0x10206000&lt;br /&gt;
| NewKernel11&lt;br /&gt;
| [http://infocenter.arm.com/help/topic/com.arm.doc.ddi0424d/index.html CoreLink™ DMA-330 r1p2] (eight channels). This is the DMA engine actually being used by the New3DS ARM11 kernel.&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background: red&amp;quot; | No&lt;br /&gt;
| A11&lt;br /&gt;
| [[MVD Registers]]&lt;br /&gt;
| 0x10207000&lt;br /&gt;
| [[MVD Services]]&lt;br /&gt;
| New 3DS only?&lt;br /&gt;
|-style=&amp;quot;border-top: double&amp;quot;&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11&lt;br /&gt;
| AXI&lt;br /&gt;
| 0x1020F000&lt;br /&gt;
| TwlBg, [[GSP Services]]&lt;br /&gt;
| [https://developer.arm.com/documentation/ddi0422/d/programmers-model/register-summary CoreLink™ NIC-301 r1p2].&lt;br /&gt;
|-style=&amp;quot;border-top: double&amp;quot;&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11&lt;br /&gt;
| AHB (or AXI?) FIFOs region&lt;br /&gt;
| 0x10300000-0x10340000&lt;br /&gt;
|&lt;br /&gt;
| Pages present in this region correspond to the same respective devices in the 0x10100000-0x10140000 region but don&#039;t hold the same registers. They hold the FIFOs instead: the HASH FIFO register is located at 0x10301000. The LgyFb scaler data FIFO are located at 0x10310000 (top) and 0x10311000 (bot), etc. Needed for DMA.&lt;br /&gt;
|-style=&amp;quot;border-top: double&amp;quot;&lt;br /&gt;
| style=&amp;quot;background: green&amp;quot; | Yes&lt;br /&gt;
| A11&lt;br /&gt;
| [[GPU/External_Registers|GPU Registers]]&lt;br /&gt;
| 0x10400000&lt;br /&gt;
| Boot11, Kernel11, [[GSP Services]]&lt;br /&gt;
||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
IO registers starting at physical address 0x10200000 are not accessible from the ARM9 (which includes all LCD/GPU registers). It seems IO registers below physical address 0x10100000 are not accessible from the ARM11 bus.&lt;br /&gt;
&lt;br /&gt;
ARM11 kernel virtual address mappings for these registers varies for different builds. For ARM11 user mode applications you have:&lt;br /&gt;
 physaddr = virtaddr - 0x1EC00000 + 0x10100000&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=NCCH&amp;diff=23480</id>
		<title>NCCH</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=NCCH&amp;diff=23480"/>
		<updated>2025-05-09T21:22:55Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:File formats]]&lt;br /&gt;
The following text tries to document the structure of the NCCH (Nintendo Content Container Header) container format. This format is used to store the content of any installed [[Titles|title]].&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
There are two known NCCH container specializations used on the 3DS, &amp;quot;executable&amp;quot; and &amp;quot;non-executable&amp;quot;, officially known as CXI and CFA, respectively.&lt;br /&gt;
&lt;br /&gt;
=== CXI ===&lt;br /&gt;
&lt;br /&gt;
The CXI (CTR Executable Image) specialization of the NCCH container contains executable code, which runs on a single ARM11 core.&lt;br /&gt;
&lt;br /&gt;
The CXI format is structured in the following order:&lt;br /&gt;
* first a NCCH header,&lt;br /&gt;
* followed by an [[NCCH/Extended Header|extended header]],&lt;br /&gt;
* followed by an access descriptor,&lt;br /&gt;
* followed by an &#039;&#039;&#039;optional&#039;&#039;&#039; plain binary region,&lt;br /&gt;
* followed by an &#039;&#039;&#039;optional&#039;&#039;&#039; executable filesystem ([[ExeFS]]) - (contains ARM11 code, Home menu [[SMDH|icn]]/bnr and [[Logo|logo]]),&lt;br /&gt;
* and finally followed by an &#039;&#039;&#039;optional&#039;&#039;&#039; read-only filesystem ([[RomFS]]) - (Used for external file storage).&lt;br /&gt;
&lt;br /&gt;
The extended header contains additional information regarding access control. &lt;br /&gt;
The plain binary region is an area specifically stored in plaintext, mostly containing SDK library strings for identification.&lt;br /&gt;
&lt;br /&gt;
=== CFA ===&lt;br /&gt;
&lt;br /&gt;
The CFA (CTR File Archive) specialization of the NCCH container is not executable, but is used in conjunction with CXI files, e.g. the DLP Child Container and the Electronic Manual. (There is a system update NCCH which follows this format, but is used by the 3DS rather than the Application NCCH, and only works when embedded in the [[CCI]] format because the nVer is kept in the header of retail [[CCI]] files instead of the application NCCH). There are CFA files which exist alone in a title, but these are just [[Title list|System Data Archive]] titles and are found only in the [[Flash Filesystem#CTR partition|NAND]].&lt;br /&gt;
&lt;br /&gt;
CFA files are structured in the following order:&lt;br /&gt;
* first a NCCH header,&lt;br /&gt;
* followed by an &#039;&#039;&#039;optional&#039;&#039;&#039; executable filesystem ([[ExeFS]]) (same as in CXI, except no ARM11 code)&lt;br /&gt;
* followed by an &#039;&#039;&#039;optional&#039;&#039;&#039; read-only filesystem ([[RomFS]])&lt;br /&gt;
&lt;br /&gt;
== Container File Format ==&lt;br /&gt;
&lt;br /&gt;
=== Encryption ===&lt;br /&gt;
The [[NCCH/Extended Header|extended header]], the [[ExeFS]], and the [[RomFS]] are encrypted using [https://github.com/3dshax/ctr/blob/master/ctrtool/ncch.c 128-bit AES CTR] unless the NoCrypto flag is set in ncchflag[7]. There are different sets of encryption parameters in use, as over the time new system updates introduced more sophisticated means of encryption. &lt;br /&gt;
&lt;br /&gt;
All encrypted regions are grouped into two categories, each of which uses one kind of encryption scheme:&lt;br /&gt;
&lt;br /&gt;
* The &amp;quot;menu info&amp;quot; group, including the [[NCCH/Extended Header|extended header]], the [[ExeFS]] header, and the files &amp;quot;icon&amp;quot; and &amp;quot;banner&amp;quot; in [[ExeFS]], which are needed to display the game on the menu regardless whether system version supports the game, or whether the pre-downloaded eshop game is officially released.&lt;br /&gt;
&lt;br /&gt;
* The &amp;quot;content&amp;quot; group, including the rest files (&amp;quot;.code&amp;quot; and &amp;quot;.firm&amp;quot;) in [[ExeFS]], and the entire [[RomFS]], which is needed for actually running the game, and which can be only decrypted on the supported system (and when the seed is officially released for eshop games).&lt;br /&gt;
&lt;br /&gt;
The decryption key is generated using the [[AES|AES Engine]]. The keyX and keyY for each group are set as follows:&lt;br /&gt;
 &lt;br /&gt;
* The &amp;quot;menu info&amp;quot; group uses the primary key, always generated by keyX in the slot 0x2C (set by bootrom) and keyY from the first 0x10 bytes of the NCCH signature.&lt;br /&gt;
&lt;br /&gt;
* The &amp;quot;content&amp;quot; group uses the secondary key. The slot selection for keyX depends on ncchflag[3], as listed in the table below. &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  ncchflag[3]&lt;br /&gt;
!  FW Introduced&lt;br /&gt;
!  Old3DS&lt;br /&gt;
!  [[AES#Keyslot|AES Keyslots]]&lt;br /&gt;
!  Notes&lt;br /&gt;
|-&lt;br /&gt;
|  0x00&lt;br /&gt;
|  The initial version&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  0x2C&lt;br /&gt;
|  This keyslot is initialized by bootrom. This is the same key as the primary key.&lt;br /&gt;
|-&lt;br /&gt;
|  0x01&lt;br /&gt;
|  [[7.0.0-13|7.0.0-X]]&lt;br /&gt;
|  style=&amp;quot;background: #ccffbb&amp;quot; | Yes&lt;br /&gt;
|  0x25&lt;br /&gt;
|  This keyslot is [[Savegames|initialized]] by the 6.0 gamecard savegame keyY init function during boot, using a different portion of the [[Savegames|final]] hash (this keyslot is separate from the one used for the 6.0 save crypto).&lt;br /&gt;
|-&lt;br /&gt;
|  0x0A&lt;br /&gt;
|  [[9.3.0-21|9.3.0-X]]&lt;br /&gt;
|  style=&amp;quot;background: #ffccbb&amp;quot; | No&lt;br /&gt;
|  0x18&lt;br /&gt;
|  This keyslot is initialized by [[FIRM#New_3DS_FIRM|arm9loader]] on New3DS starting with [[8.1.0-0_New3DS]], but only [[9.3.0-21|9.3.0-X]] and later know how to use it with ncchflag[3].&lt;br /&gt;
|-&lt;br /&gt;
|  0x0B&lt;br /&gt;
|  [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
|  style=&amp;quot;background: #ffccbb&amp;quot; | No&lt;br /&gt;
|  0x1B&lt;br /&gt;
|  [[9.6.0-24|9.6.0-X]]&#039;s [[FIRM#New_3DS_FIRM|arm9loader]] changed the contents of keyslots 0x19-0x1F; 9.6.0-X was the first time they were officially used, so this is not a breaking change (there is no content that would use the old versions of those keys).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Besides all rules above, if ncchflag[7] bitmask 0x1 is set, and a fixed AES key instead is used for both groups. There are two fixed keys, one for titles which have the system category bit set (SystemFixedKey), and one for the rest (&amp;quot;zeros&amp;quot; key). These are debug keys, as they aren&#039;t nomally supported on retail systems.&lt;br /&gt;
&lt;br /&gt;
The initial CTR for decryption each region is generated from the partition ID, as described below:&lt;br /&gt;
&lt;br /&gt;
* if the version field (NCCH header + 0x112) is 0 or 2, the 16-byte CTR is [partition_id[7], partition_id[6], ..., partition_id[0], M, 0, ..., 0], where&lt;br /&gt;
** The 8-byte partition ID starts from the beginning in the reverse order. If the partition ID is viewed as a little-endian u64, and the CTR is viewed as big-endian u128, then this is to put the partition ID to the most significant bits of the CTR&lt;br /&gt;
** CTR[8] is set to a magic number M. For [[NCCH/Extended Header|extended header]], M = 1. For [[ExeFS]], M = 2. For [[RomFS]], M = 3.&lt;br /&gt;
** The rest 7 bytes (the least significant bits of big-endian CTR) are set to zero&lt;br /&gt;
&lt;br /&gt;
* if the version field is 1, the 16-byte CTR is [partition_id[0], partition_id[1], ...,partition_id[7], 0, 0, 0, 0, T[0], T[1], T[2], T[3]], where&lt;br /&gt;
** The 8-byte partition ID starts from the beginning in the same order. &lt;br /&gt;
** Then four zeros follow.&lt;br /&gt;
** Then a number T in big-endian follows. This number is the offset of each encrypted region to the NCCH beginning. So for [[NCCH/Extended Header|extended header]], T = 0x200. For [[ExeFS]]/[[RomFS]]. T = 0x200 * ExeFS/RomFS offset in media units&lt;br /&gt;
&lt;br /&gt;
Note that due to the key generation schemes described above, ExeFS can contain regions using different keys. Regardless of this, both regions shared the same initial CTR at the beginning of ExeFS. If one region doesn&#039;t start at the beginning of ExeFS, its actual CTR at its own beginning = ExeFS CTR + (region offset / AESBlockSize=16)&lt;br /&gt;
&lt;br /&gt;
=== Format ===&lt;br /&gt;
&lt;br /&gt;
Currently, only [[ExeFS]]:/.code can be compressed (with a LZ77 variant). A flag in the [[NCCH/Extended Header#System Info|exheader]] determines if this is the case.&lt;br /&gt;
&lt;br /&gt;
On retail for SD applications, exheader_systeminfoflags.flag bit1 must be set.&lt;br /&gt;
&lt;br /&gt;
Retail CFAs use the default NCCH product code &amp;quot;CTR-P-CTAP&amp;quot;, while retail title/gamecard CXIs use NCCH product code &amp;quot;CTR-X-XXXX&amp;quot;. This product code is the NCCH [[Serials|serial code]]. The region-locking info checked by home menu is stored in the [[SMDH#BNR Region|icon]].&lt;br /&gt;
&lt;br /&gt;
All of the hashes stored in this NCCH header are over the cleartext data. The ExeFS/RomFS superblock starts at offset 0x0 in the ExeFS/RomFS, and the size is specified by the hash region fields. Nintendo&#039;s NCCH validation code seems to have the size of this region fixed to 0x200 bytes (for ExeFS at least). &lt;br /&gt;
&lt;br /&gt;
As of [[5.0.0-11]] the application [[ExeFS]]:/logo can be loaded from the plaintext region between the access descriptor and the plain region, all applications built since [[5.0.0-11]] store the logo here. The size of this logo is always 0x2000-bytes.&lt;br /&gt;
&lt;br /&gt;
The plain region mainly contains tags for each SDK library used when building the CXI. The version used for the &amp;quot;FIRMWARE&amp;quot; tag is the kernel/FIRM [[Configuration_Memory|version]], this version can also be stored in the exheader &amp;quot;kernel release version&amp;quot; ARM11 kernel descriptor field. As of [[2.2.0-X]] the NATIVE_FIRM kernels check the CXI exheader &amp;quot;kernel release version&amp;quot; field, if it is stored in the CXI exheader. If the kernel/FIRM version specified by this field is higher than the version of the running NATIVE_FIRM, the kernel will return error-code 0xD9001413.&lt;br /&gt;
&lt;br /&gt;
=== NCCH Header ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Offset&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
|  0x000&lt;br /&gt;
|  0x100&lt;br /&gt;
|  RSA-2048 signature of the NCCH header, using SHA-256.&lt;br /&gt;
|-&lt;br /&gt;
|  0x100&lt;br /&gt;
|  4&lt;br /&gt;
|  Magic ID, always &#039;NCCH&#039;&lt;br /&gt;
|-&lt;br /&gt;
|  0x104&lt;br /&gt;
|  4&lt;br /&gt;
|  Content size, in media units (1 media unit = 0x200 bytes)&lt;br /&gt;
|-&lt;br /&gt;
|  0x108&lt;br /&gt;
|  8&lt;br /&gt;
|  Partition ID&lt;br /&gt;
|-&lt;br /&gt;
|  0x110&lt;br /&gt;
|  2&lt;br /&gt;
|  Maker code&lt;br /&gt;
|-&lt;br /&gt;
|  0x112&lt;br /&gt;
|  2&lt;br /&gt;
|  Version&lt;br /&gt;
|-&lt;br /&gt;
|  0x114&lt;br /&gt;
|  4&lt;br /&gt;
|  When ncchflag[7] = 0x20 starting with FIRM [[9.6.0-24|9.6.0-X]], this is compared with the first output u32 from a SHA256 hash. The data used for that hash is 0x18-bytes: &amp;lt;0x10-long title-unique content lock seed&amp;gt; &amp;lt;programID from NCCH+0x118&amp;gt;. This hash is only used for verification of the content lock seed, and is not the actual keyY.&lt;br /&gt;
|-&lt;br /&gt;
|  0x118&lt;br /&gt;
|  8&lt;br /&gt;
|  Program ID&lt;br /&gt;
|-&lt;br /&gt;
|  0x120&lt;br /&gt;
|  0x10&lt;br /&gt;
|  Reserved&lt;br /&gt;
|-&lt;br /&gt;
|  0x130&lt;br /&gt;
|  0x20&lt;br /&gt;
|  Logo Region SHA-256 hash. (For applications built with SDK 5+) (Supported from firmware: [[5.0.0-11]])&lt;br /&gt;
|-&lt;br /&gt;
|  0x150&lt;br /&gt;
|  0x10&lt;br /&gt;
|  Product code&lt;br /&gt;
|-&lt;br /&gt;
|  0x160&lt;br /&gt;
|  0x20&lt;br /&gt;
|  Extended header SHA-256 hash (SHA256 of 2x Alignment Size, beginning at 0x0 of ExHeader)&lt;br /&gt;
|-&lt;br /&gt;
|  0x180&lt;br /&gt;
|  4&lt;br /&gt;
|  Extended header size, in bytes&lt;br /&gt;
|-&lt;br /&gt;
|  0x184&lt;br /&gt;
|  4&lt;br /&gt;
|  Reserved&lt;br /&gt;
|-&lt;br /&gt;
|  0x188&lt;br /&gt;
|  8&lt;br /&gt;
|  Flags (See Below)&lt;br /&gt;
|-&lt;br /&gt;
|  0x190&lt;br /&gt;
|  4&lt;br /&gt;
|  Plain region offset, in media units&lt;br /&gt;
|-&lt;br /&gt;
|  0x194&lt;br /&gt;
|  4&lt;br /&gt;
|  Plain region size, in media units&lt;br /&gt;
|-&lt;br /&gt;
|  0x198&lt;br /&gt;
|  4&lt;br /&gt;
|  Logo Region offset, in media units (For applications built with SDK 5+) (Supported from firmware: [[5.0.0-11]])&lt;br /&gt;
|-&lt;br /&gt;
|  0x19c&lt;br /&gt;
|  4&lt;br /&gt;
|  Logo Region size, in media units (For applications built with SDK 5+) (Supported from firmware: [[5.0.0-11]])&lt;br /&gt;
|-&lt;br /&gt;
|  0x1A0&lt;br /&gt;
|  4&lt;br /&gt;
|  ExeFS offset, in media units&lt;br /&gt;
|-&lt;br /&gt;
|  0x1A4&lt;br /&gt;
|  4&lt;br /&gt;
|  ExeFS size, in media units&lt;br /&gt;
|-&lt;br /&gt;
|  0x1A8&lt;br /&gt;
|  4&lt;br /&gt;
|  ExeFS hash region size, in media units&lt;br /&gt;
|-&lt;br /&gt;
|  0x1AC&lt;br /&gt;
|  4&lt;br /&gt;
|  Reserved&lt;br /&gt;
|-&lt;br /&gt;
|  0x1B0&lt;br /&gt;
|  4&lt;br /&gt;
|  RomFS offset, in media units&lt;br /&gt;
|-&lt;br /&gt;
|  0x1B4&lt;br /&gt;
|  4&lt;br /&gt;
|  RomFS size, in media units&lt;br /&gt;
|-&lt;br /&gt;
|  0x1B8&lt;br /&gt;
|  4&lt;br /&gt;
|  RomFS hash region size, in media units&lt;br /&gt;
|-&lt;br /&gt;
|  0x1BC&lt;br /&gt;
|  4&lt;br /&gt;
|  Reserved&lt;br /&gt;
|-&lt;br /&gt;
|  0x1C0&lt;br /&gt;
|  0x20&lt;br /&gt;
|  ExeFS superblock SHA-256 hash - (SHA-256 hash, starting at 0x0 of the ExeFS over the number of media units specified in the ExeFS hash region size)&lt;br /&gt;
|-&lt;br /&gt;
|  0x1E0&lt;br /&gt;
|  0x20&lt;br /&gt;
|  RomFS superblock SHA-256 hash - (SHA-256 hash, starting at 0x0 of the RomFS over the number of media units specified in the RomFS hash region size)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Given offsets are based on the start of the file.&lt;br /&gt;
&lt;br /&gt;
=== NCCH Flags ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  INDEX&lt;br /&gt;
!  DESCRIPTION&lt;br /&gt;
|-&lt;br /&gt;
|  3&lt;br /&gt;
|  Crypto Method: When this is non-zero, a NCCH crypto method using two keyslots is used(see above).&lt;br /&gt;
|-&lt;br /&gt;
|  4&lt;br /&gt;
|  Content Platform: 1 = CTR, 2 = snake (New 3DS).&lt;br /&gt;
|-&lt;br /&gt;
|  5&lt;br /&gt;
|  Content Type Bit-masks: Data = 0x1, Executable = 0x2, SystemUpdate = 0x4, Manual = 0x8, Child = (0x4&amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt;0x8), Trial = 0x10. When &#039;Data&#039; is set, but not &#039;Executable&#039;, NCCH is a CFA. Otherwise when &#039;Executable&#039; is set, NCCH is a CXI.&lt;br /&gt;
|-&lt;br /&gt;
|  6&lt;br /&gt;
|  Content Unit Size i.e. u32 ContentUnitSize = 0x200*2^flags[6]; &lt;br /&gt;
|-&lt;br /&gt;
|  7&lt;br /&gt;
|  Bit-masks: FixedCryptoKey = 0x1, NoMountRomFs = 0x2, NoCrypto = 0x4, using a new keyY generator = 0x20(starting with FIRM [[9.6.0-24|9.6.0-X]]).&lt;br /&gt;
|}&lt;br /&gt;
CXIs NCCH header signature is verified using the RSA modulus stored in the exheader, while CFAs NCCH header is verified with a fixed RSA modulus. The public exponent is 65537 for both.&lt;br /&gt;
&lt;br /&gt;
==== NCCH header example for Lego Starwars III ====&lt;br /&gt;
 Signature:              720FF8F83F2A1E998322A026D1434165&lt;br /&gt;
                         ED19642ABC1CB2722135AA202BEAD60A&lt;br /&gt;
                         80BCD21C768C597B8268FEF2C64EA710&lt;br /&gt;
                         4C9BA5E12CFFBD1D0C619F4EF7B42CA7&lt;br /&gt;
                         DD8482CB4EB26720AD66CDA57ABCBCFB&lt;br /&gt;
                         D63268A6E2896A59B3B744E39E45B88A&lt;br /&gt;
                         ABB4C0980ACC6210818DCE6DAC838A10&lt;br /&gt;
                         95D0F66B352474D4B3DA4B333F49912D&lt;br /&gt;
                         29AF7EA58BC8C890B18C70B7D540A9FB&lt;br /&gt;
                         EBE24A5312055617D3353B28C3EB1D17&lt;br /&gt;
                         61021BEFF6AD22C384835B40BD44DFAD&lt;br /&gt;
                         981F6350F9458B17BCB5F768C92ABC93&lt;br /&gt;
                         2BCE9888855A8998F4CDE40C9543514A&lt;br /&gt;
                         C57B84EB75A680E7C742632614620D1D&lt;br /&gt;
                         A253284DF3DC01091EB3800C36FD62EE&lt;br /&gt;
                         BA15340F1FD498FAB67C0302E9CDA397&lt;br /&gt;
 Magic:                  NCCH&lt;br /&gt;
 Content size:           0x1cfef400&lt;br /&gt;
 Partition id:           0004000000038c00&lt;br /&gt;
 Maker code:             3436 (&amp;quot;46&amp;quot;)&lt;br /&gt;
 Version:                0002&lt;br /&gt;
 Program id:             0004000000038c00&lt;br /&gt;
 Temp flag:              00&lt;br /&gt;
 Product code:           CTR-P-ALGP&lt;br /&gt;
 Extended header hash:   0C27E3C1DE7B2AE2D3114F32A4EEBF46&lt;br /&gt;
                         9AFD0CF352C11D4984C2A9F1D2144C63&lt;br /&gt;
 Extended header size:   00000400&lt;br /&gt;
 Flags:                  0000030100000000&lt;br /&gt;
 Plain region offset:    0x00004a00&lt;br /&gt;
 Plain region size:      0x00000200&lt;br /&gt;
 ExeFS offset:           0x00004c00&lt;br /&gt;
 ExeFS size:             0x00143800&lt;br /&gt;
 ExeFS hash region size: 0x00000200&lt;br /&gt;
 RomFS offset:           0x00148400&lt;br /&gt;
 RomFS size:             0x1ceab000&lt;br /&gt;
 RomFS hash region size: 0x00000200&lt;br /&gt;
 ExeFS Superblock Hash:  130C042615F647C4C63225EA9E67F8A2&lt;br /&gt;
                         7B15246B88FBC7A927257B84977B787B&lt;br /&gt;
 RomFS Superblock Hash:  A65BEE1060BB6A6821BBCEC600035B7E&lt;br /&gt;
                         64FB6EACA7F0960CFB1F5A37087728F7&lt;br /&gt;
 Note: Offsets and sizes in media units have been converted to byte sizes.&lt;br /&gt;
&lt;br /&gt;
==== Plain region example for Lego Starwars III ====&lt;br /&gt;
 0004a00: 5b 53 44 4b 2b 4e 49 4e 54 45 4e 44 4f 3a 43 54  [SDK+NINTENDO:CT    [SDK+NINTENDO:CTR_SDK-0_14_23_200_none]&lt;br /&gt;
 0004a10: 52 5f 53 44 4b 2d 30 5f 31 34 5f 32 33 5f 32 30  R_SDK-0_14_23_20&lt;br /&gt;
 0004a20: 30 5f 6e 6f 6e 65 5d 00 5b 53 44 4b 2b 4e 49 4e  0_none].[SDK+NIN    [SDK+NINTENDO:Firmware-02_27]&lt;br /&gt;
 0004a30: 54 45 4e 44 4f 3a 46 69 72 6d 77 61 72 65 2d 30  TENDO:Firmware-0&lt;br /&gt;
 0004a40: 32 5f 32 37 5d 00 5b 53 44 4b 2b 4d 6f 62 69 63  2_27].[SDK+Mobic    [SDK+Mobiclip:Deblocker_1_0_2]&lt;br /&gt;
 0004a50: 6c 69 70 3a 44 65 62 6c 6f 63 6b 65 72 5f 31 5f  lip:Deblocker_1_&lt;br /&gt;
 0004a60: 30 5f 32 5d 00 5b 53 44 4b 2b 4d 6f 62 69 63 6c  0_2].[SDK+Mobicl    [SDK+Mobiclip:ImaAdpcmDec_1_0_0]&lt;br /&gt;
 0004a70: 69 70 3a 49 6d 61 41 64 70 63 6d 44 65 63 5f 31  ip:ImaAdpcmDec_1&lt;br /&gt;
 0004a80: 5f 30 5f 30 5d 00 5b 53 44 4b 2b 4d 6f 62 69 63  _0_0].[SDK+Mobic    [SDK+Mobiclip:MobiclipDec_1_0_1]&lt;br /&gt;
 0004a90: 6c 69 70 3a 4d 6f 62 69 63 6c 69 70 44 65 63 5f  lip:MobiclipDec_&lt;br /&gt;
 0004aa0: 31 5f 30 5f 31 5d 00 5b 53 44 4b 2b 4d 6f 62 69  1_0_1].[SDK+Mobi    [SDK+Mobiclip:MoflexDemuxer_1_0_2]&lt;br /&gt;
 0004ab0: 63 6c 69 70 3a 4d 6f 66 6c 65 78 44 65 6d 75 78  clip:MoflexDemux&lt;br /&gt;
 0004ac0: 65 72 5f 31 5f 30 5f 32 5d 00 00 00 00 00 00 00  er_1_0_2].......&lt;br /&gt;
 0004ad0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................&lt;br /&gt;
 0004ae0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................&lt;br /&gt;
&lt;br /&gt;
==== Example dependency list from the extended header ====&lt;br /&gt;
 00850 41 50 54 3A 55 00 00 00 24 68 69 6F 46 49 4F 00 APT:U...$hioFIO.&lt;br /&gt;
 00860 24 68 6F 73 74 69 6F 30 24 68 6F 73 74 69 6F 31 $hostio0$hostio1&lt;br /&gt;
 00870 61 63 3A 75 00 00 00 00 62 6F 73 73 3A 55 00 00 ac:u....boss:U..&lt;br /&gt;
 00880 63 61 6D 3A 75 00 00 00 63 65 63 64 3A 75 00 00 cam:u...cecd:u..&lt;br /&gt;
 00890 63 66 67 3A 75 00 00 00 64 6C 70 3A 46 4B 43 4C cfg:u...dlp:FKCL&lt;br /&gt;
 008A0 64 6C 70 3A 53 52 56 52 64 73 70 3A 3A 44 53 50 dlp:SRVRdsp::DSP&lt;br /&gt;
 008B0 66 72 64 3A 75 00 00 00 66 73 3A 55 53 45 52 00 frd:u...fs:USER.&lt;br /&gt;
 008C0 67 73 70 3A 3A 47 70 75 68 69 64 3A 55 53 45 52 gsp::Gpuhid:USER&lt;br /&gt;
 008D0 68 74 74 70 3A 43 00 00 6D 69 63 3A 75 00 00 00 http:C..mic:u...&lt;br /&gt;
 008E0 6E 64 6D 3A 75 00 00 00 6E 65 77 73 3A 75 00 00 ndm:u...&amp;lt;nowiki&amp;gt;news:u..&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
 008F0 6E 77 6D 3A 3A 55 44 53 70 74 6D 3A 75 00 00 00 nwm::UDSptm:u...&lt;br /&gt;
 00900 70 78 69 3A 64 65 76 00 73 6F 63 3A 55 00 00 00 pxi:dev.soc:U...&lt;br /&gt;
 00910 73 73 6C 3A 43 00 00 00 79 32 72 3A 75 00 00 00 ssl:C...y2r:u...&lt;br /&gt;
 00920 69 72 3A 55 53 45 52 00 6C 64 72 3A 72 6F 00 00 ir:USER.ldr:ro..&lt;br /&gt;
 00930 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................&lt;br /&gt;
 00940 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................&lt;br /&gt;
 00950 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................&lt;br /&gt;
 00960 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................&lt;br /&gt;
 ... ...&lt;br /&gt;
 009D0 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF                 &lt;br /&gt;
 009E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................&lt;br /&gt;
 009F0 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 02 . .............&lt;br /&gt;
&lt;br /&gt;
== Tools ==&lt;br /&gt;
&lt;br /&gt;
[https://github.com/profi200/Project_CTR/tree/master/ctrtool ctrtool] - (CMD)(Windows/Linux) Parsing and decrypting (debug only) NCCH files&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=NCCH/Extended_Header&amp;diff=23479</id>
		<title>NCCH/Extended Header</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=NCCH/Extended_Header&amp;diff=23479"/>
		<updated>2025-05-09T21:20:34Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page documents the format of the &#039;&#039;&#039;NCCH Extended Header&#039;&#039;&#039;, or &#039;&#039;&#039;exheader&#039;&#039;&#039; for short.&lt;br /&gt;
&lt;br /&gt;
The exheader has two sections:&lt;br /&gt;
&lt;br /&gt;
* The actual exheader data, containing System Control Info (SCI) and Access Control Info (ACI);&lt;br /&gt;
* A signed copy of NCCH HDR public key, and exheader ACI. This version of the ACI is used as limitation to the actual ACI.&lt;br /&gt;
&lt;br /&gt;
== Main Structure ==&lt;br /&gt;
All values are little endian unless otherwise specified.&lt;br /&gt;
&lt;br /&gt;
See also: [https://github.com/3DSGuy/Project_CTR/blob/20f708450b9c6e7f64eafa6c2a8eeb25a630c69a/ctrtool/exheader.h]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Offset&lt;br /&gt;
! Size&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x0&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x200&amp;lt;/code&amp;gt;&lt;br /&gt;
| SCI&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x200&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x200&amp;lt;/code&amp;gt;&lt;br /&gt;
| ACI&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x400&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x100&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;AccessDesc&amp;lt;/code&amp;gt; signature (RSA-2048-SHA256)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x500&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x100&amp;lt;/code&amp;gt;&lt;br /&gt;
| NCCH header RSA-2048 modulus&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x600&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x200&amp;lt;/code&amp;gt;&lt;br /&gt;
| ACI (for limitation of first ACI)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AccessDesc&amp;lt;/code&amp;gt; signature covers the NCCH header modulus and second ACI. The &amp;lt;code&amp;gt;AccessDesc&amp;lt;/code&amp;gt; public key is initialised by the boot ROM.&lt;br /&gt;
&lt;br /&gt;
When loading the exheader, [[FIRM|Process9]] compares the exheader data with the data in the &amp;lt;code&amp;gt;AccessDesc&amp;lt;/code&amp;gt; (note that not everything is compared here). When these don&#039;t match, an error is returned. The Process9 code handling this validation was updated with [[6.0.0-11|v6.0]]; the only change in this function seems to be the check for the &amp;quot;Ideal processor&amp;quot; field.&lt;br /&gt;
&lt;br /&gt;
== System Control Info ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Offset&lt;br /&gt;
! Size&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x0&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x8&amp;lt;/code&amp;gt;&lt;br /&gt;
| Application title (default is &amp;quot;CtrApp&amp;quot;)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x8&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x5&amp;lt;/code&amp;gt;&lt;br /&gt;
| Reserved&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0xD&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x1&amp;lt;/code&amp;gt;&lt;br /&gt;
| Flag (bit 0: &amp;lt;code&amp;gt;CompressExefsCode&amp;lt;/code&amp;gt;, bit 1: &amp;lt;code&amp;gt;SDApplication&amp;lt;/code&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0xE&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x2&amp;lt;/code&amp;gt;&lt;br /&gt;
| Remaster version&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x10&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0xC&amp;lt;/code&amp;gt;&lt;br /&gt;
| Text code set info&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x1C&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x4&amp;lt;/code&amp;gt;&lt;br /&gt;
| Stack size&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x20&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0xC&amp;lt;/code&amp;gt;&lt;br /&gt;
| Read-only code set info&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x2C&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x4&amp;lt;/code&amp;gt;&lt;br /&gt;
| Reserved&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x30&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0xC&amp;lt;/code&amp;gt;&lt;br /&gt;
| Data code set info&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x3C&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x4&amp;lt;/code&amp;gt;&lt;br /&gt;
| BSS size&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x40&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x180&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;48*8&amp;lt;/code&amp;gt;)&lt;br /&gt;
| Dependency [[Title list#00040130 - System Modules|module]] (program ID) list&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x1C0&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x40&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;SystemInfo&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Most of these fields are used in [[LOADER:LoadProcess]].&lt;br /&gt;
&lt;br /&gt;
=== Code Set Info ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Offset&lt;br /&gt;
! Size&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x0&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;4&amp;lt;/code&amp;gt;&lt;br /&gt;
| Address&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x4&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;4&amp;lt;/code&amp;gt;&lt;br /&gt;
| Physical region size (in page-multiples)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x8&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;4&amp;lt;/code&amp;gt;&lt;br /&gt;
| Size (in bytes)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== System Info ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Offset&lt;br /&gt;
! Size&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x0&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x8&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;SaveData&amp;lt;/code&amp;gt; Size&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x8&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x8&amp;lt;/code&amp;gt;&lt;br /&gt;
| Jump ID&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x10&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x30&amp;lt;/code&amp;gt;&lt;br /&gt;
| Reserved&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Access Control Info ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Offset&lt;br /&gt;
! Size&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x0&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x170&amp;lt;/code&amp;gt;&lt;br /&gt;
| [[#ARM11 Local System Capabilities|ARM11 local system capabilities]]&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x170&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x80&amp;lt;/code&amp;gt;&lt;br /&gt;
| [[#ARM11 Kernel Capabilities|ARM11 kernel capabilities]]&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x1F0&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x10&amp;lt;/code&amp;gt;&lt;br /&gt;
| [[#ARM9 Access Control|ARM9 access control]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== ARM11 Local System Capabilities ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Offset&lt;br /&gt;
! Size&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x0&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x8&amp;lt;/code&amp;gt;&lt;br /&gt;
| Program ID&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x8&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x4&amp;lt;/code&amp;gt;&lt;br /&gt;
| Core version (The Title ID low of the required [[FIRM]])&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0xC&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x2&amp;lt;/code&amp;gt;&lt;br /&gt;
| [[#Flag1|Flag1]] and [[#Flag2|Flag2]] (both implemented starting from [[8.0.0-18]]).&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0xE&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x1&amp;lt;/code&amp;gt;&lt;br /&gt;
| [[#Flag0|Flag0]]&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0xF&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x1&amp;lt;/code&amp;gt;&lt;br /&gt;
| Priority&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x10&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x20&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;16*2&amp;lt;/code&amp;gt;)&lt;br /&gt;
| Resource limit descriptors. The first byte here controls the maximum allowed [[PMApp:SetAppResourceLimit|&amp;lt;code&amp;gt;CpuTime&amp;lt;/code&amp;gt;]].&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x30&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x20&amp;lt;/code&amp;gt;&lt;br /&gt;
| [[#Storage Info|Storage info]]&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x50&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x100&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;32*8&amp;lt;/code&amp;gt;)&lt;br /&gt;
| [[#Service Access Control|Service access control]]&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x150&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x10&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;2*8&amp;lt;/code&amp;gt;)&lt;br /&gt;
| Extended service access control, support for this was implemented with [[9.3.0-21|9.3.0-X]].&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x160&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0xF&amp;lt;/code&amp;gt;&lt;br /&gt;
| Reserved&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x16F&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x1&amp;lt;/code&amp;gt;&lt;br /&gt;
| Resource limit category. (0 = &amp;lt;code&amp;gt;APPLICATION&amp;lt;/code&amp;gt;, 1 = &amp;lt;code&amp;gt;SYS_APPLET&amp;lt;/code&amp;gt;, 2 = &amp;lt;code&amp;gt;LIB_APPLET&amp;lt;/code&amp;gt;, 3 = &amp;lt;code&amp;gt;OTHER&amp;lt;/code&amp;gt; (sysmodules running under the BASE memregion))&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== Flag0 ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0-1&amp;lt;/code&amp;gt;&lt;br /&gt;
| Ideal processor&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;2-3&amp;lt;/code&amp;gt;&lt;br /&gt;
| Affinity mask&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;4-7&amp;lt;/code&amp;gt;&lt;br /&gt;
| Old3DS system mode&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===== Old3DS System Mode =====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;Prod&amp;lt;/code&amp;gt; (64MB of usable application memory)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;Undefined&amp;lt;/code&amp;gt; (unusable)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;Dev1&amp;lt;/code&amp;gt; (96MB of usable application memory)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;3&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;Dev2&amp;lt;/code&amp;gt; (80MB of usable application memory)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;4&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;Dev3&amp;lt;/code&amp;gt; (72MB of usable application memory)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;5&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;Dev4&amp;lt;/code&amp;gt; (32MB of usable application memory)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;6-7&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;Undefined&amp;lt;/code&amp;gt; Same as &amp;lt;code&amp;gt;Prod&amp;lt;/code&amp;gt;?&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
In the exheader data, the ideal processor field is a bit-index, while in the &amp;lt;code&amp;gt;AccessDesc&amp;lt;/code&amp;gt; the ideal processor field is a bitmask. When the bit specified by the exheader field is not set in the &amp;lt;code&amp;gt;AccessDesc&amp;lt;/code&amp;gt; field, an error is returned.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;if((1 &amp;lt;&amp;lt; exheaderval) &amp;amp; accessdescval == 0) return error&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
During a FIRM-launch when a &amp;lt;code&amp;gt;TitleInfo&amp;lt;/code&amp;gt; structure was specified, the field at offset [[FIRM#FIRM_Launch_Parameters|0x400]] in the FIRM-launch parameters is set to the SystemMode of the specified title, however in some cases other values are written there. With [[8.0.0-18]] NS will now check the output of [[PTM|PTMSYSM]] command &amp;lt;code&amp;gt;0x040A0000&amp;lt;/code&amp;gt;, when the output is non-zero and a certain NS state field is value-zero, the following is executed otherwise this is skipped. With that check passed on [[8.0.0-18]], NS will then check (&amp;lt;code&amp;gt;Flag2 &amp;amp; 0xF&amp;lt;/code&amp;gt;). When that is &amp;lt;code&amp;gt;value2&amp;lt;/code&amp;gt;, the output value (used for the FIRM-launcher parameter field mentioned above) is set to &amp;lt;code&amp;gt;value7&amp;lt;/code&amp;gt;. Otherwise, when that value is non-zero, the output value is set to 6.&lt;br /&gt;
&lt;br /&gt;
==== Flag1 ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Bits&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;EnableL2Cache&amp;lt;/code&amp;gt; (Unknown what this actually does, New3DS-only presumably)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;cpuspeed_804MHz&amp;lt;/code&amp;gt; (Default &amp;quot;cpuspeed&amp;quot; when not set)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;2-7&amp;lt;/code&amp;gt;&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
In order for the exheader to have any of the above new bits set, the &amp;lt;code&amp;gt;AccessDesc&amp;lt;/code&amp;gt; must have the corresponding bit set, otherwise the invalid-exheader error is returned.&lt;br /&gt;
&lt;br /&gt;
Homebrew which runs under a title which has the above &amp;lt;code&amp;gt;cpuspeed&amp;lt;/code&amp;gt; flag set, runs much faster on New3DS. It&#039;s unknown how exactly the system handles these flags.&lt;br /&gt;
&lt;br /&gt;
When launching titles / perhaps other things with [[APT]], [[NS]] uses [[PTMSYSM:ConfigureNew3DSCPU]] with data which originally came from these flags; NS does this regardless of what the running 3DS system is. However, due to a bug(?) in NS the value sent to that command is always either 0x0 or 0x3. When calculating that value, the code only ever uses the cpuspeed field, not the cache field: code to actually load and check the value of the cache field appears to be missing.&lt;br /&gt;
&lt;br /&gt;
==== Flag2 ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Bit&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0-3&amp;lt;/code&amp;gt;&lt;br /&gt;
| New3DS system mode&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;4-7&amp;lt;/code&amp;gt;&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===== New3DS System Mode =====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Value&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;Legacy&amp;lt;/code&amp;gt; (use Old3DS system mode)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;Prod&amp;lt;/code&amp;gt; (124MB of usable application memory)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;Dev1&amp;lt;/code&amp;gt; (178MB of usable application memory)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;3&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;Dev2&amp;lt;/code&amp;gt; (124MB of usable application memory)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;4-7&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;Undefined&amp;lt;/code&amp;gt; Same as &amp;lt;code&amp;gt;Prod&amp;lt;/code&amp;gt;?&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When in &amp;lt;code&amp;gt;Legacy&amp;lt;/code&amp;gt; mode, the actual memory layout is the same as in &amp;lt;code&amp;gt;New3DS Prod&amp;lt;/code&amp;gt;, except the available application memory as reported to the application is reduced to the Old3DS size.&lt;br /&gt;
&lt;br /&gt;
The exheader value for the New3DS system mode value must be ≤ to the &amp;lt;code&amp;gt;AccessDesc&amp;lt;/code&amp;gt; value, otherwise the invalid-exheader error is returned.&lt;br /&gt;
&lt;br /&gt;
==== Storage Info ====&lt;br /&gt;
Used in [[FSReg:Register]].&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Offset&lt;br /&gt;
! Size&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x0&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;8&amp;lt;/code&amp;gt;&lt;br /&gt;
| Extdata ID&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x8&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;8&amp;lt;/code&amp;gt;&lt;br /&gt;
| System savedata IDs&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x10&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;8&amp;lt;/code&amp;gt;&lt;br /&gt;
| Storage accessible unique IDs&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x18&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;7&amp;lt;/code&amp;gt;&lt;br /&gt;
| Filesystem access info&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x1F&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
| Other attributes&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
File System Access Info:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Bit and bitmask&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x1&amp;lt;/code&amp;gt;&lt;br /&gt;
| Category system application&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x2&amp;lt;/code&amp;gt;&lt;br /&gt;
| Category hardware check&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x4&amp;lt;/code&amp;gt;&lt;br /&gt;
| Category filesystem tool&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;3&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x8&amp;lt;/code&amp;gt;&lt;br /&gt;
| Debug&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;4&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x10&amp;lt;/code&amp;gt;&lt;br /&gt;
| TWL card backup&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;5&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x20&amp;lt;/code&amp;gt;&lt;br /&gt;
| TWL NAND data&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;6&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x40&amp;lt;/code&amp;gt;&lt;br /&gt;
| BOSS&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;7&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x80&amp;lt;/code&amp;gt;&lt;br /&gt;
| [[FS:OpenArchive|&amp;lt;code&amp;gt;sdmc:/&amp;lt;/code&amp;gt;]]&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;8&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x100&amp;lt;/code&amp;gt;&lt;br /&gt;
| Core&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;9&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x200&amp;lt;/code&amp;gt;&lt;br /&gt;
| [[Flash Filesystem|&amp;lt;code&amp;gt;nand:/ro/&amp;lt;/code&amp;gt;]] (Read Only)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;10&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x400&amp;lt;/code&amp;gt;&lt;br /&gt;
| [[Flash Filesystem|&amp;lt;code&amp;gt;nand:/rw/&amp;lt;/code&amp;gt;]]&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;11&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x800&amp;lt;/code&amp;gt;&lt;br /&gt;
| [[Flash Filesystem|&amp;lt;code&amp;gt;nand:/ro/&amp;lt;/code&amp;gt;]] (Write Access)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;12&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x1000&amp;lt;/code&amp;gt;&lt;br /&gt;
| Category system settings&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;13&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x2000&amp;lt;/code&amp;gt;&lt;br /&gt;
| Cardboard&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;14&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x4000&amp;lt;/code&amp;gt;&lt;br /&gt;
| Export/Import IVS&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;15&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x8000&amp;lt;/code&amp;gt;&lt;br /&gt;
| [[FS:OpenArchive|&amp;lt;code&amp;gt;sdmc:/&amp;lt;/code&amp;gt;]] (Write-only)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;16&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x10000&amp;lt;/code&amp;gt;&lt;br /&gt;
| Switch cleanup (Introduced in [[3.0.0-5|3.0.0]]?) &lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;17&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x20000&amp;lt;/code&amp;gt;&lt;br /&gt;
| Savedata move (Introduced in [[5.0.0-11|5.0.0]]) &lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;18&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x40000&amp;lt;/code&amp;gt;&lt;br /&gt;
| Shop (Introduced in [[5.0.0-11|5.0.0]]) &lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;19&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x80000&amp;lt;/code&amp;gt;&lt;br /&gt;
| Shell (Introduced in [[5.0.0-11|5.0.0]]) &lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;20&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x100000&amp;lt;/code&amp;gt;&lt;br /&gt;
| Category home menu (Introduced in [[6.0.0-11|6.0.0]])&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;21&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x200000&amp;lt;/code&amp;gt;&lt;br /&gt;
| Seed DB. Introduced in [[9.6.0-24|9.6.0-X]] [[FIRM]]. [[Home Menu]] has this bit set starting with [[9.6.0-24|9.6.0-X]].&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
====Other Attributes====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Bit&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
| Not use ROMFS&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
| Use Extended savedata access.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When Bit1 is set, the &amp;quot;Extdata ID&amp;quot; and &amp;quot;Storage Accessable Unique IDs&amp;quot; regions are used to store a total of 6 &amp;quot;Accessible Save IDs&amp;quot;. Introduced in [[6.0.0-11|6.0.0]].&lt;br /&gt;
&lt;br /&gt;
==== Service Access Control ====&lt;br /&gt;
This is the list of [[Services_API|services]] which the process is allowed to access, this is registered with the [[Services|services]] manager. Each service listed in the exheader must be listed in the &amp;lt;code&amp;gt;AccessDesc&amp;lt;/code&amp;gt;, otherwise the invalid exheader error is returned. The order of the services for exheader/&amp;lt;code&amp;gt;AccessDesc&amp;lt;/code&amp;gt; doesn&#039;t matter. The &amp;lt;code&amp;gt;AccessDesc&amp;lt;/code&amp;gt; can list services which are not in the exheader, but normally the service-access-control data for exheader/&amp;lt;code&amp;gt;AccessDesc&amp;lt;/code&amp;gt; are exactly the same.&lt;br /&gt;
&lt;br /&gt;
This list is submitted to [[SRVPM:RegisterProcess]].&lt;br /&gt;
&lt;br /&gt;
=== ARM11 Kernel Capabilities ===&lt;br /&gt;
The kernel capability descriptors are passed to [[SVC|svcCreateProcess]].&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Offset&lt;br /&gt;
! Size&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x0&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x70&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;28*4&amp;lt;/code&amp;gt;)&lt;br /&gt;
| Descriptors&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x70&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;0x10&amp;lt;/code&amp;gt;&lt;br /&gt;
| Reserved&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
There are different descriptor types, determined by the number of leading ones in the binary value representation of bits 20-31. The different types are laid out as follows:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Pattern of bits 20-31&lt;br /&gt;
! Type&lt;br /&gt;
! Fields&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0b1110xxxxxxxx&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interrupt info&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0b11110xxxxxxx&amp;lt;/code&amp;gt;&lt;br /&gt;
| System call mask&lt;br /&gt;
| Bits 24-26: System call mask table index; Bits 0-23: mask&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0b1111110xxxxx&amp;lt;/code&amp;gt;&lt;br /&gt;
| Kernel release version&lt;br /&gt;
| Bits 8-15: Major version; Bits 0-7: Minor version&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0b11111110xxxx&amp;lt;/code&amp;gt;&lt;br /&gt;
| Handle table size&lt;br /&gt;
| Bits 0-18: size&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0b111111110xxx&amp;lt;/code&amp;gt;&lt;br /&gt;
| [[#ARM11_Kernel_Flags|Kernel flags]]&lt;br /&gt;
| See below&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0b11111111100x&amp;lt;/code&amp;gt;&lt;br /&gt;
| Map IO/static address range&lt;br /&gt;
| Describes a memory mapping like the 0b111111111110 descriptor, but an entire range rather than a single page is mapped. Another 0b11111111100x descriptor must follow this one to denote the (exclusive) end of the address range to map. Bit20 on the first descriptor: map read-only (otherwise RW), bit20 on the second descriptor: map static (cacheable, otherwise IO if the bit is not set)  &lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0b111111111110&amp;lt;/code&amp;gt;&lt;br /&gt;
| Map IO memory page&lt;br /&gt;
| Bits 0-19: page index to map (virtual address &amp;gt;&amp;gt; 12; the physical address is determined per-page according to [[Memory layout]]); Bit 20: Map read-only (otherwise read-write)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== ARM11 Kernel Flags ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Bit&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
| Allow debug&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
| Force debug&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt;&lt;br /&gt;
| Allow non-alphanum&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;3&amp;lt;/code&amp;gt;&lt;br /&gt;
| Shared page writing&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;4&amp;lt;/code&amp;gt;&lt;br /&gt;
| Privilege priority&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;5&amp;lt;/code&amp;gt;&lt;br /&gt;
| Allow &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt; args&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;6&amp;lt;/code&amp;gt;&lt;br /&gt;
| Shared device memory&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;7&amp;lt;/code&amp;gt;&lt;br /&gt;
| Runnable on sleep&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;8-11&amp;lt;/code&amp;gt;&lt;br /&gt;
| Memory type (1: application, 2: system, 3: base)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;12&amp;lt;/code&amp;gt;&lt;br /&gt;
| [[Memory_layout#NATIVE_FIRM.2FSAFE_MODE_FIRM_Userland_Memory|Special memory]]&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;13&amp;lt;/code&amp;gt;&lt;br /&gt;
| Process has access to CPU core 2 (New3DS only)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== ARM9 Access Control ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Offset&lt;br /&gt;
! Size&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0x0&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;15&amp;lt;/code&amp;gt;&lt;br /&gt;
| Descriptors&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0xF&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
| ARM9 Descriptor Version. Originally this value had to be ≥ 2. Starting with [[9.3.0-21|9.3.0-X]] this value has to be either value 2 or value 3.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Descriptors:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Bit&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
| Mount [[Flash Filesystem|&amp;lt;code&amp;gt;nand:/&amp;lt;/code&amp;gt;]]&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
| Mount [[Flash Filesystem|&amp;lt;code&amp;gt;nand:/ro/&amp;lt;/code&amp;gt;]] (Write Access)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt;&lt;br /&gt;
| Mount [[Flash Filesystem|&amp;lt;code&amp;gt;twln:/&amp;lt;/code&amp;gt;]]&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;3&amp;lt;/code&amp;gt;&lt;br /&gt;
| Mount [[Flash Filesystem|&amp;lt;code&amp;gt;wnand:/&amp;lt;/code&amp;gt;]]&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;4&amp;lt;/code&amp;gt;&lt;br /&gt;
| Mount card SPI&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;5&amp;lt;/code&amp;gt;&lt;br /&gt;
| Use SDIF3&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;6&amp;lt;/code&amp;gt;&lt;br /&gt;
| Create seed&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;7&amp;lt;/code&amp;gt;&lt;br /&gt;
| Use card SPI&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;8&amp;lt;/code&amp;gt;&lt;br /&gt;
| SD application (Not checked)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;9&amp;lt;/code&amp;gt;&lt;br /&gt;
| Mount [[SD Filesystem|sdmc:/]] (Write Access)&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=3DS_System_Flaws&amp;diff=23440</id>
		<title>3DS System Flaws</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=3DS_System_Flaws&amp;diff=23440"/>
		<updated>2025-05-02T05:05:02Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Exploits are used to execute unofficial code (homebrew) on the Nintendo 3DS. This page is a list of publicly known system flaws, for userland applications/applets flaws see [[3DS_Userland_Flaws|here]].&lt;br /&gt;
&lt;br /&gt;
=Stale / Rejected Efforts=&lt;br /&gt;
* In the early days of 3DS hacking, Neimod was working on a RAM dumping setup for a while. He has de-soldered the 3DS&#039;s RAM chip and hooked it and the RAM pinouts on the 3DS&#039;s PCB up to a custom RAM dumping setup. He &#039;&#039;has&#039;&#039; published photos showing his setup to be working quite well, with the 3DS successfully booting up, but however, his flickr stream is now private along with most of his work and this method has been unreleased. RAM dumping can be done through homebrew now, making this method obsolete regardless.&lt;br /&gt;
&lt;br /&gt;
==Tips and info==&lt;br /&gt;
The 3DS uses the XN feature of the ARM11 processor. There&#039;s no official way from applications to enable executable permission for memory containing arbitrary unsigned code(there&#039;s a [[SVC]] for this, but only [[RO_Services|RO-module]] has access to it). A usable userland exploit would still be useful: you could only do return-oriented-programming with it initially. From ROP one could then exploit system flaw(s), see below.&lt;br /&gt;
&lt;br /&gt;
SD card [[extdata]] and SD savegames can be attacked, for consoles where the console-unique [[Nand/private/movable.sed|movable.sed]] was dumped(accessing SD data is far easier by running code on the target 3DS however).&lt;br /&gt;
&lt;br /&gt;
=System flaws=&lt;br /&gt;
== Hardware ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Fixed with hardware model/revision&lt;br /&gt;
!  Newest hardware model/revision this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| ARM9/ARM11 bootrom vectors point at uninitialized RAM&lt;br /&gt;
| ARM9&#039;s and ARM11&#039;s exception vectors are hardcoded to point at the CPU&#039;s internal memory (0x08000000 region for ARM9, AXIWRAM for ARM11). While the bootrom does set them up to point to an endless loop at some point during boot, it does not do so immediately. As such, a carefully-timed fault injection (via hardware) to trigger an exception (such as an invalid instruction) will cause execution to fall into ARM9 RAM. &lt;br /&gt;
Since RAM isn&#039;t cleared on boot (see below), one can immediately start execution of their own code here to dump bootrom, OTP, etc.&lt;br /&gt;
The ARM9 bootrom does the following at reset:  reset vector branches to another instruction, then branches to bootrom+0x8000. Hence, there&#039;s no way to know for certain when exactly the ARM9 exception-vector data stored in memory gets initialized.&lt;br /&gt;
&lt;br /&gt;
The vulnerable timing range is about 100 CPU cycles after they start (which happens after the PLLs have stabilized after power-up). A glitch needs to be injected during one of these 100 cycles for the attack to succeed.&lt;br /&gt;
&lt;br /&gt;
It has been exploited by derrek to dump the ARM9 bootrom as of Summer 2015.&lt;br /&gt;
| None: all available 3DS models at the time of writing have the exact same ARM9/ARM11 bootrom for the unprotected areas.&lt;br /&gt;
| New3DS&lt;br /&gt;
| End of February 2014&lt;br /&gt;
| [[User:Derrek|derrek]], WulfyStylez (May 2015) independently&lt;br /&gt;
|-&lt;br /&gt;
| Missing AES key clearing&lt;br /&gt;
| The hardware AES engine does not clear keys when doing a hard reset/reboot.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| August 2014&lt;br /&gt;
| Mathieulh/Others&lt;br /&gt;
|-&lt;br /&gt;
| No RAM clearing on reboots&lt;br /&gt;
| On an MCU-triggered reboot all RAM including FCRAM/ARM9 memory/AXIWRAM/VRAM keeps its contents.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| March 2014&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| 32bits of actual console-unique TWLNAND keydata&lt;br /&gt;
| On retail the 8-bytes at ARM9 address [[Memory_layout|0x01FFB808]] are XORed with hard-coded data, to generate the TWL console-unique keys, including TWLNAND. On Old3DS the high u32 is always 0x0, while on New3DS that u32 is always 0x2. On top of this, the lower u32&#039;s highest bit is always ORed. only 31 bits of the TWL console-unique keydata / TWL consoleID are actually console-unique.&lt;br /&gt;
This allows one to easily bruteforce the TWL console-unique keydata with *just* data from TWLNAND. On DSi the actual console-unique data for key generation is 8-bytes(all bytes actually set).&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| 2012?&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| DSi / 3DS-TWL key-generator&lt;br /&gt;
| After using the key generator to generate the normal-key, you could overwrite parts of the normal-key with your own data and then recover the key-generator output by comparing the new crypto output with the original crypto output. From the normal-key outputs, you could deduce the TWL key-generator function.&lt;br /&gt;
This applies to the keyX/keyY too.&lt;br /&gt;
&lt;br /&gt;
This attack does not work for the 3DS key-generator because keyslots 0-3 are only for TWL keys.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| 2011&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| 3DS key-generator&lt;br /&gt;
| The algorithm for generating the normal-keys for keyslots is cryptographically weak.  As a result, it is easily susceptible to differential cryptanalysis if the normal-key corresponding to any scrambler-generated keyslot is discovered.&lt;br /&gt;
&lt;br /&gt;
Several such pairs of matching normal-keys and KeyY values were found, leading to deducing the key-generator function.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| February 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]], [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| RSA keyslots don&#039;t clear exponent when setting modulus&lt;br /&gt;
| The [[RSA_Registers|RSA keyslots]] are set by boot ROM to have four private RSA keys.  The exponent value in the RSA registers is write-only and not readable.&lt;br /&gt;
&lt;br /&gt;
However, when setting a keyslot&#039;s modulus, the RSA hardware leaves the exponent alone.  This allows retrieving the exponent by doing a discrete logarithm of the output.&lt;br /&gt;
&lt;br /&gt;
By setting the modulus to a prime number whose modular multiplicative order is &amp;quot;smooth&amp;quot; (that is, p-1 is divisible by only small prime numbers), discrete logarithms can be calculated quickly using the [[wikipedia:Pohlig-Hellman algorithm|Pohlig-Hellman algorithm]].  If the prime chosen is greater than the modulus, but the same bit size, the discrete logarithm is the private exponent.&lt;br /&gt;
&lt;br /&gt;
This exploit&#039;s usefulness is limited: RSA keyslot 0 is only used in current firmware for deriving the 6.x save and 7.x NCCH keys, which were already known, and the other three keyslots are entirely unused.  Additionally, with a boot ROM dump, this exploit is moot; these private keys are located in the protected ARM9 boot ROM.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| March 2016&lt;br /&gt;
| [[User:Myria|Myria]]&lt;br /&gt;
|-&lt;br /&gt;
| [[CONFIG11_Registers#CFG11_GPUPROT|CFG11_GPUPROT]] allowing acccess to AXIWRAM/FCRAM-BASE-memregion&lt;br /&gt;
| [[CONFIG11_Registers#CFG11_GPUPROT|CFG11_GPUPROT]] can be configured by anything with access to it to allow the GPU to access the entire AXIWRAM+FCRAM. For example, this is an issue for any sysmodule that gets exploited and has access to this register memory-page(include one that&#039;s listed below).&lt;br /&gt;
&lt;br /&gt;
See also &amp;quot;kernelhax via gspwn&amp;quot; below.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| February 7, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Boot ROM ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Fixed with hardware model/revision&lt;br /&gt;
!  Newest hardware model/revision this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| FIRM partitions known-plaintext&lt;br /&gt;
| The [[Flash_Filesystem|FIRM partitions]] are encrypted with AES-CTR without a MAC. Since this works by XOR&#039;ing data with a static (per-console in this case) keystream, one can deduce the keystream of a portion of each FIRM partition if they have the actual FIRM binary stored in it.&lt;br /&gt;
&lt;br /&gt;
This can be paired with many exploits. For example, it allows minor FIRM downgrades (i.e. 10.4 to 9.6 or 9.5 to 9.4, but not 9.6 to 9.5).&lt;br /&gt;
However it is most commonly used to install arbitrary FIRMs (usually boot9strap), thanks to sighax.&lt;br /&gt;
&lt;br /&gt;
This can be somewhat addressed by having a FIRM header skip over previously used section offsets, but this would just air-gap newer FIRMs without fixing the core bug. This can also only be done a limited number of times due to the size of FIRM versus the size of the partitions.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| &lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| Boot9 AES keyinit function issues&lt;br /&gt;
| [[Bootloader|Boot9]] seems to have two bugs in the AES key-init function, see [[AES_Registers#AES_key-init|here]].&lt;br /&gt;
| None&lt;br /&gt;
| BootROM issue.&lt;br /&gt;
| 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| New3DS has same boot ROM as Old3DS&lt;br /&gt;
| The New3DS has the exact same boot ROM as the Old3DS.  This means, among other things, that all the same boot ROM flaws are present.  Also, this meant that it is possible to boot Old3DS firmware on New3DS (see &amp;quot;CFG9_SYSPROT9 bit1 not set by Kernel9&amp;quot;).&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| October 2014&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| sighax: Boot9 improper validation of FIRM partition RSA signatures&lt;br /&gt;
| The [[Flash_Filesystem|FIRM partitions]] are signed with RSA-2048 using SHA-256 and PKCS #1 v1.5 padding.  Boot9, however, improperly validates the padding in three ways:&lt;br /&gt;
# Boot9 permits block type 02, meant for encrypted messages, to be used for signatures.  Only 01, for signatures, should have been permitted.  As a result, when using block type 02, a signature block is not required to have a long string of FF bytes as padding, but rather any nonzero random values suffice.&lt;br /&gt;
# Boot9 does not require that the length of the padding fill out the signature block completely.  As a result, there is considerable freedom in the layout of a signature.&lt;br /&gt;
# Boot9 fails to do bounds checking in its parsing of the DER-encoded hash algorithm type and hash value; the length values given in DER are permitted to point outside the signature block.&lt;br /&gt;
Flaw 3 allows the DER encoding to be such that boot9 believes that the signature&#039;s hash value is outside the range of the block itself, somewhere on the stack.  This can be pointed at the correct hash value it computes.  Boot9 then memcmp&#039;s the calculated hash against itself, and thinks that the hash is valid.&lt;br /&gt;
&lt;br /&gt;
As a result of the above, we estimate that one in 2&amp;lt;sup&amp;gt;43&amp;lt;/sup&amp;gt; (~8.8 trillion) random fake signatures will be considered by Boot9 to be valid.  This is well within the range of brute force, particularly with an optimized GPU implementation.  An Nvidia GTX 1080 Ti would take about one week to find a match.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| July 2015&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| Boot9 FIRM loading doesn&#039;t blacklist memory-mapped I/O&lt;br /&gt;
| [[Bootloader|Boot9]]&#039;s FIRM loading blacklists Boot9 data regions, but forgets to do other important regions, including Memory-mapped I/O. Combined with sighax, a malicious FIRM can be used to overwrite:&lt;br /&gt;
a) boot9 data-abort handler, coupled with a 4th section that tries to NDMA copy to NULL, causing a data abort&lt;br /&gt;
&lt;br /&gt;
b) boot9 IRQ handler (this has the disadvantage that you must restore the original handler, then call it manually when your payload runs)&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| 2015(?)&lt;br /&gt;
| [[User:Derrek|derrek]] (2015?), [[User:Normmatt|Normmatt]] and [[User:SciresM|SciresM]] independently (January 2017).&lt;br /&gt;
|-&lt;br /&gt;
| &amp;quot;superhax&amp;quot;: Boot9 FIRM loading blacklist check is flawed&lt;br /&gt;
| Boot9 only makes sure the &#039;&#039;&#039;start&#039;&#039;&#039; and &#039;&#039;&#039;end&#039;&#039;&#039; address of each section is not covered by a blacklisted region. Thus, it is possible to overwrite blacklisted regions (e.g. ARM9 Exception Vectors) by choosing a FIRM section range that encloses an entire blacklisted region. The vulnerable code looks like this: if(blRegions[i].start &amp;lt;= sectionStart &amp;amp;&amp;amp; blRegions[i].end &amp;gt; sectionStart &amp;lt;nowiki&amp;gt;||&amp;lt;/nowiki&amp;gt; blRegions[i].start &amp;lt;= sectionEnd &amp;amp;&amp;amp; blRegions[i].end &amp;gt; sectionEnd) return false; // failure&lt;br /&gt;
The boot9 vector table (0x08000000) contains 6 entries, each 8-bytes wide (0x30 bytes); Only 0x08000000 through 0x08000040 are blacklisted, and boot9 doesn&#039;t use the region after the vector table (this is convenient because we can put any payload we want after it and not worry about overwriting chunks of boot9 code)&lt;br /&gt;
&lt;br /&gt;
To exploit this, craft a FIRM section payload that&#039;s loaded a few bytes before 0x08000000, add padding to get to 0x08000000 and overwrite the vector table; You could overwrite the data-abort vector and craft a 4th FIRM section that causes a data-abort OR you can just overwrite the IRQ function pointer at 0x08000004 (make sure your payload replaces the original boot9 function pointer); you can point the rest of the vectors to infinite loops since they shouldn&#039;t be triggered.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| August 2015&lt;br /&gt;
| [[User:Plutoo|plutoo]], [[User:Yellows8|yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ARM9 software ==&lt;br /&gt;
&lt;br /&gt;
=== arm9loader ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Public disclosure timeframe&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| Generating the keysector console-unique keys with ITCM+Boot9&lt;br /&gt;
| [[Bootloader|Boot9]] decrypts the 0x100-byte [[OTP_Registers|OTP]] using AES-CBC with keydata stored in Boot9. If hash verification is successful, the plaintext of the first 0x90-bytes are copied into [[Memory_layout|ITCM]]. This is the &#039;&#039;exact&#039;&#039; &#039;&#039;same&#039;&#039; region hashed by arm9loader when generating the console-unique keys for decrypting the keysector, except arm9loader uses the raw encrypted OTP.&lt;br /&gt;
&lt;br /&gt;
Therefore, with the OTP keydata+IV from Boot9 you can: encrypt the 0x90-bytes from ITCM, then hash the output to get the console-unique keys for the system&#039;s keysector. This can even be done for Old3DS which doesn&#039;t have the arm9loader keysector officially.&lt;br /&gt;
&lt;br /&gt;
It&#039;s unknown why arm9loader only used the first 0x90-bytes of OTP. Using more data from OTP would&#039;ve prevented this. Fixing this would require doing exactly that, but that would also mean updating the NAND keysector(which is dangerous).&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| &lt;br /&gt;
| 2015&lt;br /&gt;
| January 6, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Rearrangable keys in the NAND keystore&lt;br /&gt;
| Due to the keystore being encrypted with AES-ECB, one can rearrange blocks and still have the NAND keystore decrypt in a deterministic way. &lt;br /&gt;
&lt;br /&gt;
Using 10.0 FIRM it is possible to rearrange keys such that ARM9 memory is executed. As such using existing ARM9 execution 10.0 FIRM can be written to NAND and a payload written to memory, with the payload to be executed post-K9L using an MCU reboot.&lt;br /&gt;
| arm9loaderhax given existing ARM9 code execution&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| Early 2016&lt;br /&gt;
| 27 September 2016&lt;br /&gt;
| Myria, [[User:Dark samus|dark_samus]]; mathieulh (independently); [[User:Plutooo|plutoo]] (independently) + others&lt;br /&gt;
|-&lt;br /&gt;
| Uncleared OTP hash keydata in console-unique 0x11 key-generation&lt;br /&gt;
| Kernel9Loader does not clear the [[SHA_Registers#SHA_HASH|SHA_HASH register]] after use. As a result, the data stored here as K9L hands over to Kernel9 is the hash of [[OTP_Registers|OTP data]] used to seed the [[FIRM#New_3DS_FIRM|console-unique NAND keystore decryption key]] set on keyslot 0x11.&lt;br /&gt;
&lt;br /&gt;
Retrieving this keydata and the [[Flash_Filesystem#0x12C00|NAND keystore]] of the same device allows calculating the decrypted New3DS NAND keystore (non-unique, common to all New3DS units), which contains AES normal keys, also set on keyslot 0x11, which are then used to derive all current [[AES_Registers#Keyslots|New3DS-only AES keyXs]] including the newer batch introduced in [[9.6.0-24#arm9loader|9.6.0-X]]. From there, it is trivial to perform the same key derivation in order to initialize those keys on any system version, and even on Old3DS.&lt;br /&gt;
&lt;br /&gt;
This can be performed by exploiting the &amp;quot;arm9loaderhax&amp;quot; vulnerability to obtain post-K9L code execution after an MCU reboot (the bootrom section-loading fail is not relevant here, this attack was performed without OTP data by brute-forcing keys), and using this to dump the SHA_HASH register. This attack works on any FIRM version shipping a vulnerable version of K9L, whereas OTP dumping required a boot of &amp;lt;[[3.0.0-6|3.0.0-X]].&lt;br /&gt;
&lt;br /&gt;
This attack results in obtaining the entire (0x200-bytes) NAND keystore - it was confirmed at a later date that this keystore is encrypted with the same key (by comparing the decrypted data from multiple units), and therefore using another key in this store will not remedy the issue as all keys are known (i.e. later, unused keys decrypt to the same 0x200-bytes constant with the same OTP hash). Later keys could have been encrypted differently but this is not the case. As a result of this, it is not possible for Nintendo to use K9L again in its current format for its intended purpose, though this was not news from the moment people dumped a New3DS OTP.&lt;br /&gt;
| Derivation of all New3DS keys generated via the NAND keystore (0x1B &amp;quot;Secure4&amp;quot; etc.)&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| ~April 2015, implemented in May 2015&lt;br /&gt;
| 13 January 2016&lt;br /&gt;
| [[User:WulfyStylez|WulfyStylez]], [[User:Dazzozo|Dazzozo]], [[User:Shinyquagsire23|shinyquagsire23]] (complimentary + implemented), [[User:Plutooo|plutoo]], Normmatt (discovered independently)&lt;br /&gt;
|-&lt;br /&gt;
| enhanced-arm9loaderhax&lt;br /&gt;
| See the 32c3 3ds talk.&lt;br /&gt;
Since this is a combination of a trick with the arm9-bootrom + arm9loaderhax, and since you have to manually write FIRM to the firm0/firm1 NAND partitions, this can&#039;t be completely fixed. Any system with existing ARM9 code execution and an OTP/OTP hash dump can exploit this. Additionally, by using the FIRM partition known-plaintext bug and bruteforcing the second entry in the keystore, this can currently be exploited on all New3DS systems without any other prerequisite hacks.&lt;br /&gt;
| arm9loaderhax which automatically occurs at hard-boot.&lt;br /&gt;
| See arm9loaderhax / description.&lt;br /&gt;
| See arm9loaderhax / description.&lt;br /&gt;
| Theorized around mid July, 2015. Later implemented+tested by [[User:Plutooo|plutoo]] and [[User:Derrek|derrek]].&lt;br /&gt;
| 32c3 3ds talk (December 27, 2015)&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| arm9loaderhax: Missing verification block for the 9.6 keys&lt;br /&gt;
| Starting with [[9.6.0-24|9.6.0-X]] a new set of NAND-based keys were introduced. However, no verification block was added to verify that the new key read from NAND is correct. This was technically an issue from [[9.5.0-22|9.5.0-X]] with the original sector+0 keydata, however the below is only possible with [[9.6.0-24|9.6.0-X]] since keyslots 0x15 and 0x16 are generated from different 0x11 keyXs.&lt;br /&gt;
&lt;br /&gt;
Writing an incorrect key to NAND will cause arm9loader to decrypt the ARM9 kernel as garbage and then jump to it.&lt;br /&gt;
&lt;br /&gt;
This allows an hardware-based attack where you can boot into an older exploited firmware, fill all memory with NOP sleds/jump-instructions, and then reboot into executing garbage. By automating this process with various input keydata, eventually you&#039;ll find some garbage that jumps to your code.&lt;br /&gt;
&lt;br /&gt;
This gives very early ARM9 code execution (pre-ARM9 kernel). As such, it is possible to dump RSA keyslots with this and calculate the 6.x [[Savegames#6.0.0-11_Savegame_keyY|save]], and 7.x [[NCCH]] keys. This cannot be used to recover keys initialized by arm9loader itself. This is due to it wiping the area used for its stack during NAND sector decryption and keyslot init. &lt;br /&gt;
&lt;br /&gt;
Due to FIRMs on both Old and New 3DS using the same RSA data, this can be exploited on Old3DS as well, but only if one already has the actual plaintext normalkey from New3DS NAND sector 0x96 offset-0 and has dumped the OTP area of the Old3DS.&lt;br /&gt;
| Recovery of 6.x [[Savegames#6.0.0-11_Savegame_keyY|save key]]/7.x [[NCCH]] key, access to uncleared OTP hash keydata&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| arm9loader runs on Old3DS&lt;br /&gt;
| Despite being written only for New3DS, all of arm9loader runs fine on Old3DS.  It&#039;s not until booting Kernel9 that a New3DS FIRM partition would crash on an Old3DS.  As a result, if a bug exists in arm9loader to get control, it can be exploited on Old3DS by writing New3DS FIRM to the FIRM partitions.  Thus, arm9loaderhax works on both Old3DS and New3DS.&lt;br /&gt;
| arm9loader bugs also compromise Old3DS&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| Sometime in 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]] presumably&lt;br /&gt;
|-&lt;br /&gt;
| Uncleared New3DS keyslot 0x11&lt;br /&gt;
| Originally the New3DS [[FIRM]] arm9bin loader only cleared keyslot 0x11 when it gets executed at firmlaunch. This was fixed with [[9.5.0-22|9.5.0-X]] by completely clearing keyslot 0x11 immediately after the loader finishes using keyslot 0x11.&lt;br /&gt;
This means that any ARM9 code that can execute before the loader clears the keyslot at firmlaunch(including firmlaunch-hax) can get access to the uncleared keyslot 0x11, which then allows one to generate all &amp;lt;=v9.5 New3DS keyXs which are generated by keyslot 0x11.&lt;br /&gt;
&lt;br /&gt;
Therefore, to completely fix this the loader would have to generate more keys using different keyslot 0x11 keydata. This was done with [[9.6.0-24|9.6.0-X]].&lt;br /&gt;
| New3DS keyXs generation&lt;br /&gt;
| Mostly fixed with [[9.5.0-22|9.5.0-X]], completely fixed with new keys with [[9.6.0-24|9.6.0-X]].&lt;br /&gt;
| &lt;br /&gt;
| February 3, 2015 (one day after [[9.5.0-22|9.5.0-X]] release)&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Process9 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Public disclosure timeframe&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| Leak of normal-key matching a key-scrambler key&lt;br /&gt;
| New 3DS firmware versions [[8.1.0-0 New3DS|8.1.0]] through [[9.2.0-20|9.2.0]] set the encryption key for [[Amiibo]] data using a hardcoded normal-key in Process9.  In firmware [[9.3.0-21|9.3.0]], Nintendo &amp;quot;fixed&amp;quot; this by using the key scrambler instead, by calculating the keyY value for keyslot 0x39 that results in the same normal-key, then hardcoding that keyY into Process9.&lt;br /&gt;
&lt;br /&gt;
Nintendo&#039;s fix is actually the problem: Nintendo revealed the normal-key matching an unknown keyX and a known keyY.  Combined with the key scrambler using an insecure scrambling algorithm (see &amp;quot;Hardware&amp;quot; above), the key scrambler function could be deduced.&lt;br /&gt;
| Deducing the keyX for keyslot 0x39 and the key scrambler algorithm&lt;br /&gt;
| New 3DS [[9.3.0-21|9.3.0-X]], sort of&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| Sometime in 2015 after the hardware key-generator was broken.&lt;br /&gt;
| 32c3 3ds talk (December 27, 2015)&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Leak of normal-key matching a key-generator key&lt;br /&gt;
| During the 3DS&#039; development (June/July 2010) Nintendo added support installing encrypted content ([[CIA]]). Common-key index1 was intended to be a [[AES|hardware generated key]]. However while they added code to generate the key in hardware, they forgot to remove the normal-key for index1 (used elsewhere, likely old debug code). Nintendo later removed the normal key sometime before the first non-prototype firmware release.&lt;br /&gt;
&lt;br /&gt;
Knowing the keyY and the normal-key for common-key index1, the devkit key-generator algorithm can be deduced (see &amp;quot;Hardware&amp;quot; above). Additionally the remaining devkit common-keys can be generated once the common-key keyX is recovered.&lt;br /&gt;
&lt;br /&gt;
Note that the devkit key-generator was discovered to be the same as the retail key-generator.&lt;br /&gt;
| Deducing the keyX for keyslot 0x3D and hardware key-generator algorithm. Generate remaining devkit common-keys.&lt;br /&gt;
| pre-[[1.0.0-0|1.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| Shortly after the key-generator was revealed to be flawed at the 32c3 3ds talk&lt;br /&gt;
| January 20, 2016&lt;br /&gt;
| [[User:Jakcron|jakcron]]&lt;br /&gt;
|-&lt;br /&gt;
| Factory firmware is vulnerable to sighax&lt;br /&gt;
| During the 3DS&#039;s development, presumably boot9 was written (including the sighax vulnerability). This vulnerability is also present in factory firmware (and earlier, including 0.11). This was fixed in version 1.0.0-0.&lt;br /&gt;
| Deducing the mechanics of the sighax vulnerability in boot9 without having a dump of protected boot9. ARM9 code execution on factory/earlier firmware.&lt;br /&gt;
| [[1.0.0-0|1.0.0-X]]&lt;br /&gt;
| [[1.0.0-0|1.0.0-X]]&lt;br /&gt;
| May 9, 2017&lt;br /&gt;
| May 19, 2017&lt;br /&gt;
| [[User:SciresM|SciresM]], [[User:Myria|Myria]]&lt;br /&gt;
|-&lt;br /&gt;
| safecerthax &lt;br /&gt;
| O3DS &amp;amp; O2DS SAFE_FIRM is still vulnerable to the PXIAM:ImportCertificates flaw fixed in [[5.0.0-11]] and to SSLoth fixed in [[11.14.0-46]]. It makes it possible to spoof the official NUS update server and remotely trigger the vulnerability in SAFE_FIRM.&lt;br /&gt;
| Remote Arm9 code execution in O3DS/O2DS SAFE_FIRM&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2020&lt;br /&gt;
| December 18, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| twlhax: Corrupted SRL header leads to memory overwrite&lt;br /&gt;
| During TWL_FIRM boot, the ARM11 process TwlBg puts launcher.srl, the DSi bootloader, into FCRAM.  TWL_FIRM Process9 then parses the [http://dsibrew.org/wiki/NDS_Format SRL header] to place launcher.srl&#039;s code where DSi mode can execute it.&lt;br /&gt;
&lt;br /&gt;
DSi-mode memory is in FCRAM, but interleaved.  Each byte of DSi-mode memory also exists at some address in 3DS FCRAM space.&lt;br /&gt;
&lt;br /&gt;
Process9 does not validate the RSA signature on launcher.srl, unlike SRLs loaded from cartridge or NAND (DSiWare).  A compromised ARM11 can, in a manner similar to firmlaunchhax, send a launcher.srl with a modified SRL header.  By setting the SRL header&#039;s ARM7/ARM9 load addresses and sizes carefully, accounting for the different memory map and for DSi mode&#039;s interleaved memory, it is possible to overwrite part of Process9&#039;s stack and take control with a ROP chain.&lt;br /&gt;
&lt;br /&gt;
Fixed in 11.8.0-X by... (fill me in)&lt;br /&gt;
| ARM9 code execution (whilst still in 3DS mode)&lt;br /&gt;
| [[11.8.0-41|11.8.0-X]]&lt;br /&gt;
| [[11.8.0-41|11.8.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| August 11, 2018&lt;br /&gt;
| smea&lt;br /&gt;
|-&lt;br /&gt;
| agbhax&lt;br /&gt;
| This is the same issue as twlhax above. Legacy FIRMs share the same OS code (Arm9-side OS, Arm11 kernel), and therefore, the outdated AGB_FIRM can be tricked into executing the still vulnerable PrepareArm9ForTwl function.&lt;br /&gt;
| ARM9 code execution (whilst still in 3DS mode)&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| December 17, 2020&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| safefirmhax&lt;br /&gt;
| SAFE_MODE_FIRM is almost never updated(even when NATIVE_FIRM is updated for vuln fixes), this can be noticed by &#039;&#039;just&#039;&#039; checking 3dbrew/ninupdates title-listings.&lt;br /&gt;
&lt;br /&gt;
The fix for firmlaunch-hax was only applied to NATIVE_FIRM in [[9.5.0-22|9.5.0-X]], leaving SAFE_FIRM exploitable. With ARM11-kernel execution, one can trigger FIRM-launch in to SAFE_FIRM, do Kernel9 &amp;lt;=&amp;gt; Kernel11 sync, PXI sync and then repeat the original attack on SAFE_FIRM instead.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| 2012-2013?&lt;br /&gt;
| Wiki: January 2, 2017&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| safefirmhax 1.1&lt;br /&gt;
| Nintendo&#039;s original safefirmhax fix was flawed -- they added a global boolean that got set to true whenever a non-sysmodule title got launched (except for a hardcoded repair title id), and panic()&#039;d if that boolean was true to prevent launching safefirm after hax was active. However, because the boolean was initially false after firmlaunch -- With ARM11-kernel execution, one could FIRM-launch into NATIVE_FIRM, and then immediately FIRM-launch again into SAFE_FIRM early in NATIVE_FIRM boot before the boolean got set to true to repeat the safehax attack.&lt;br /&gt;
&lt;br /&gt;
This was fixed by adding additional CFG9_BOOTENV checks to firmlaunch code in 11.4.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[11.4.0-37|11.4.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| safefirmhax fix&lt;br /&gt;
| Wiki: April 10, 2017&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| ntrcardhax&lt;br /&gt;
| When reading the banner of a NTR title, Process9 relies on a hardware register to know when the banner was fully read.&lt;br /&gt;
However that register is shared between the ARM9 and the ARM11.&lt;br /&gt;
An attacker with k11 control can so make Process9 believe the banner continues forever and so trigger a buffer overflow.&lt;br /&gt;
With a custom banner for a NTR flashcart, this leads to code execution in Process9.&lt;br /&gt;
&lt;br /&gt;
This was fixed by adding bound checks on the read data.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[10.4.0-29|10.4.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| March 2015&lt;br /&gt;
| 32c3 3ds talk (December 27, 2015)&lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| Title downgrading via [[Application_Manager_Services|AM]]([[Application_Manager_Services_PXI|PXI]])&lt;br /&gt;
| When a title is *already* installed, Process9 will compare the installed title-version with the title-version being installed. When the one being installed is older, Process9 would return an error.&lt;br /&gt;
&lt;br /&gt;
However, this can be bypassed by just deleting the title first via the service command(s) for that: with the title removed from the [[Title_Database]], Process9 can&#039;t compare the input title-version with anything. Hence, titles can be downgraded this way.&lt;br /&gt;
&lt;br /&gt;
[[11.0.0-33|11.0.0-X]] fixed this for key system titles (MSET, Home Menu, spider, ErrDisp, SKATER, NATIVE_FIRM, and every retail system module), by checking the version of the title to install against a hard-coded list of (titleID, minimumVersionRequired) pairs.&lt;br /&gt;
| Bypassing title version check at installation, which then allows downgrading any title.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]], for key system titles.&lt;br /&gt;
| NATIVE_FIRM / AM-sysmodule [[11.0.0-33|11.0.0-X]]&lt;br /&gt;
| ?&lt;br /&gt;
| &lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| Anti-downgrade list did not include all system titles initially&lt;br /&gt;
| The anti-downgrade list did not include legacy FIRMs until [[11.8.0-41|11.8.0-X]]. Therefore, legacy FIRMs could still be downgraded.&lt;br /&gt;
| Downgrading legacy FIRMs; allowing to exploit bugs in older legacy FIRMs (of which at least one exists, see below).&lt;br /&gt;
| [[11.8.0-33|11.8.0]]&lt;br /&gt;
| [[11.8.0-33|11.8.0]]&lt;br /&gt;
| ?&lt;br /&gt;
| Wiki: August 5, 2018&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| TWL_FIRM cmd-9 unchecked offset&lt;br /&gt;
| In [[1.0.0-0|1.0.0-X]]&#039;s TWL_FIRM, cmds 8 and 9 were not stubbed (whereas in the corresponding NATIVE_FIRM, they were).&lt;br /&gt;
Command 8 does the Process9 initialisation for NTR carts if an NTR cart is inserted (NTR, not TWL, judged by chipid).&lt;br /&gt;
&lt;br /&gt;
Command 9 takes (u32 offset_read, u32 offset_write, u32 offset_read_end), and basically just copies (offset_read_end - offset_read) bytes starting at (offset_read) of [NTR cart header in arm9mem, NTR secure area in fcram, TWL secure area in fcram], to 0x18001000 + offset_write + offset_read.&lt;br /&gt;
&lt;br /&gt;
offset_write is not checked at all, thus this leads to ARM9 code execution as long as any NTR cart, including flashcarts that would normally be blocked by TWL_FIRM, is inserted.&lt;br /&gt;
&lt;br /&gt;
In [[2.0.0-2|2.0.0-X]] TWL_FIRM, those commands were stubbed out.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[2.0.0-2|2.0.0-X]]&lt;br /&gt;
| [[2.0.0-2|2.0.0-X]]&lt;br /&gt;
| January 2018&lt;br /&gt;
| Wiki: August 5, 2018&lt;br /&gt;
| [[User:Riley|Riley]]&lt;br /&gt;
|-&lt;br /&gt;
| FIRM launch doesn&#039;t check target FIRM version&lt;br /&gt;
| When executing a FIRM launch, Process9 doesn&#039;t validate that the target FIRM isn&#039;t an old version.  This allows booting an exploitable FIRM from a newer FIRM, if you can get the exploitable FIRM installed.  ([[11.0.0-33|11.0.0-X]] now prevents installing old versions of system titles, but this doesn&#039;t affect titles already installed.)&lt;br /&gt;
&lt;br /&gt;
This had a use after [[9.6.0-24|9.6.0-X]]: on a compromised 3DS running 9.2.0, you could install the 9.6.0 NATIVE_FIRM to FIRM0/FIRM1, but avoid putting it into the NATIVE_FIRM title.  This would boot the 9.2.0 system software but with the 9.6.0 Process9 and Kernel11.  With a user-mode exploit in a sufficiently-privileged application (e.g. mset), you could trigger a FIRM launch back to NATIVE_FIRM, which would load the 9.2.0 Process9 and Kernel11.&lt;br /&gt;
&lt;br /&gt;
9.6.0&#039;s keyslots 0x15 and 0x16 are unknown to 9.2.0, so 9.2.0 would not clear them.  You then could do firmlaunchhax against 9.2.0 to get ARM9 access with keyslots 0x15 and 0x16 set to their proper 9.6.0 values, allowing decrypting 9.6.0&#039;s encrypted titles.  Once the New3DS keystore was dumped, this became moot.&lt;br /&gt;
| Decrypting 9.6.0 NCCH files without dumping New3DS keystore&lt;br /&gt;
| None (but now moot)&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| August 12, 2018&lt;br /&gt;
| [[User:Yellows8|Yellows8]], [[User:Myria|Myria]]&lt;br /&gt;
|-&lt;br /&gt;
| FAT FS code null-deref&lt;br /&gt;
| When FSFile:Read is used with a file which is corrupted on a FAT filesystem(in particular SD), Process9 can crash. This particular crash is caused by a function returning NULL instead of an actual ptr due to an error. The caller of that function doesn&#039;t check for NULL which then triggers a read based at NULL.&lt;br /&gt;
&lt;br /&gt;
Sample &amp;quot;fsck.vfat -n -v -V &amp;lt;fat image backup&amp;gt;&amp;quot; output for the above crash:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;...&lt;br /&gt;
Starting check/repair pass.&lt;br /&gt;
&amp;lt;FilePath0&amp;gt; and&lt;br /&gt;
&amp;lt;FilePath1&amp;gt;&lt;br /&gt;
 share clusters.&lt;br /&gt;
 Truncating second to 3375104 bytes.&lt;br /&gt;
&amp;lt;FilePath1&amp;gt;&lt;br /&gt;
 File size is 2787392 bytes, cluster chain length is 16384 bytes.&lt;br /&gt;
 Truncating file to 16384 bytes.&lt;br /&gt;
Checking for unused clusters.&lt;br /&gt;
Reclaimed 1 unused cluster (16384 bytes).&lt;br /&gt;
Checking free cluster summary.&lt;br /&gt;
Free cluster summary wrong (1404490 vs. really 1404491)&lt;br /&gt;
 Auto-correcting.&lt;br /&gt;
Starting verification pass.&lt;br /&gt;
Checking for unused clusters.&lt;br /&gt;
Leaving filesystem unchanged.&amp;lt;/pre&amp;gt;&lt;br /&gt;
| Useless null-based-read&lt;br /&gt;
| None&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| July 8-9, 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[FS:EnumerateExtSaveData]] crashes process9 when trying to parse a file as an extdata directory in Data Management (MSET9)&lt;br /&gt;
| In the implementation for FSPXI:EnumerateExtSaveData (called by [[System_Settings|MSET]] to parse 3DS extdata IDs for Data Management), the return value of the P9 internal function call to open a directory (when enumerating contents of the extdata directory) was not checked. Therefore, if the call fails, an uninitialised pointer on stack will be used for a vtable call.&lt;br /&gt;
&lt;br /&gt;
As such, a file that starts with 8 hex digits can crash process9 if placed directly inside the extdata directory. It can crash in various ways based on subtle differences in the way the user triggers the crash event.&lt;br /&gt;
&lt;br /&gt;
While mostly leading to null derefs, in one specific context, process9 jumps directly to an ID1 string being held in ARM9 memory. Surprisingly, the 3DS doesn&#039;t discern what characters are used for the ID1 directory name on the SD, only requiring exactly 32 chars. This allows the attacker to insert arm instructions into the unicode ID1 dirname and take control of the ARM9, and thus, full control of the 3DS.&lt;br /&gt;
| ARM9 code execution (primary)&lt;br /&gt;
| None&lt;br /&gt;
| [[11.17.0-50|11.17.0-X]]&lt;br /&gt;
| April 2022&lt;br /&gt;
| August 7, 2023&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| RSA signature padding checks&lt;br /&gt;
| The TWL_FIRM RSA sig padding check code used for all TWL RSA sig-checks has issues, see [[FIRM|here]].&lt;br /&gt;
The main 3DS RSA padding check code(non-certificate, including NATIVE_FIRM) uses the function used with the above to extract more padding + the actual hash from the additional padding. This isn&#039;t really a problem here because there&#039;s proper padding check code which is executed prior to this.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.5.0-22|9.5.0-X]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[AMPXI:ValidateDSiWareSectionMAC]] [[AES_Registers|AES]] keyslot reuse&lt;br /&gt;
| When the input DSiWare section index is higher than &amp;lt;max number of DSiWare sections supported by this FIRM&amp;gt;, Process9 uses keyid 0x40 for calculating the AESMAC, which translates to keyslot 0x40. The result is that the keyslot is left at whatever was already selected before, since the AES selectkeyslot code will immediately  return when keyslot is &amp;gt;=0x40. However, actually exploiting this is difficult: the calculated AESMAC is never returned, this command just compares the calculated AESMAC with the input AESMAC(result-code depends on whether the AESMACs match). It&#039;s unknown whether a timing attack would work with this.&lt;br /&gt;
This is basically a different form of the pxips9 keyslot vuln, except with AESMAC etc.&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| March 15, 2015&lt;br /&gt;
| December 29, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| pxips9 [[AES_Registers|AES]] keyslot reuse&lt;br /&gt;
| This requires access to the [[Process_Services|ps:ps]]/pxi:ps9 services. One way to get access to this would be snshax on system-version &amp;lt;=10.1.0-X(see 32c3 3ds talk).&lt;br /&gt;
When an invalid key-type value is passed to any of the PS commands, Process9 will try to select keyslot 0x40. That aesengine_setkeyslot() code will then immediately return due to the invalid keyslot value. Since that function doesn&#039;t return any errors, Process9 will just continue to do crypto with whatever AES keyslot was selected before the PS command was sent.&lt;br /&gt;
| Reusing the previously used keyslot, for crypto with PS.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| Roughly the same time(same day?) as firmlaunch-hax.&lt;br /&gt;
| December 29, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| firmlaunch-hax: FIRM header ToCToU&lt;br /&gt;
| This can&#039;t be exploited from ARM11 userland.&lt;br /&gt;
During [[FIRM]] launch, the only FIRM header the ARM9 uses at all is stored in FCRAM, this is 0x200-bytes(the actual used FIRM RSA signature is read to the Process9 stack however). The ARM9 doesn&#039;t expect &amp;quot;anything&amp;quot; besides the ARM9 to access this data.&lt;br /&gt;
With [[9.5.0-22]] the address of this FIRM header was changed from a FCRAM address, to ARM9-only address 0x01fffc00.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[9.5.0-22]]&lt;br /&gt;
| &lt;br /&gt;
| 2012, 3 days after [[User:Yellows8|Yellows8]] started Process9 code RE.&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Uninitialized data output for (PXI) command replies&lt;br /&gt;
| PXI commands for various services(including some [[Filesystem_services_PXI|here]] and many others) can write uninitialized data (like from ARM registers) to the command reply. This happens with stubbed commands, but this can also occur with certain commands when returning an error.&lt;br /&gt;
Certain ARM11 service commands have this same issue as well.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.3.0-21|9.3.0-X]]&lt;br /&gt;
| ?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Filesystem_services_PXI|FSPXI]] OpenArchive SD permissions&lt;br /&gt;
| Process9 does not use the exheader ARM9 access-mount permission flag for SD at all.&lt;br /&gt;
This would mean ARM11-kernelmode code / fs-module itself could directly use FSPXI to access SD card without ARM9 checking for SD access, but this is rather useless since a process is usually running with SD access(Home Menu for example) anyway.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.3.0-21|9.3.0-X]]&lt;br /&gt;
| 2012&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[AMPXI:ExportDSiWare]] export path&lt;br /&gt;
| Process9 allocates memory on Process9 heap for the export path then verifies that the actual allocated size matches the input size. Then Process9 copies the input path from FCRAM to this buffer, and uses it with the Process9 FS openfile code, which use paths in the form of &amp;quot;&amp;lt;mountpoint&amp;gt;:/&amp;lt;path&amp;gt;&amp;quot;.&lt;br /&gt;
Process9 does not check the contents of this path at all before passing it to the FS code, besides writing a NUL-terminator to the end of the buffer.&lt;br /&gt;
| Exporting of DSiWare to arbitrary Process9 file-paths, such as &amp;quot;nand:/&amp;lt;path&amp;gt;&amp;quot; etc. This isn&#039;t really useful since the data which gets written can&#039;t be controlled.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.5.0-22]]&lt;br /&gt;
| April 2013&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[DSiWare_Exports]] [[CTCert]] verification&lt;br /&gt;
| Just like DSi originally did, 3DS verifies the APCert for DSiWare on SD with the CTCert also in the DSiWare .bin. On DSi this was fixed with with system-version 1.4.2 by verifying with the actual console-unique cert instead(stored in NAND), while on 3DS it&#039;s still not fixed.&lt;br /&gt;
On 3DS this is used in conjunction with seedminer to be able to decrypt &amp;amp; modify DSiWare TAD containers and inject them with exploitable DSiWare titles such as sudoku (sudokuhax) and Flipnote JPN (ugopwn)&lt;br /&gt;
| When the movable.sed keyY for the target 3DS is known and the target 3DS CTCert private-key is unknown, importing of modified DSiWare SD .bin files.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.10.0-X&lt;br /&gt;
| April 2013&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| seedminer: movable.sed keyY vulnerable to brute-force&lt;br /&gt;
| Half of the movable.sed keyY&#039;s 128 bits are leaked through the [[Nandrw/sys/LocalFriendCodeSeed_B|LFCS]], which is available in userland and below. The LFCS itself also leaks almost half of the remaining bits by following the ratio: u32 keyY[3]=1/5(LFCS). The remaining keyY[3] uncertainty of about ±2000 can be greatly reduced by plotting expected error margins with several keyYs. This results in a final uncertainty of about 2^40, easily within practical brute force range of an average modern PC.&lt;br /&gt;
| Knowing the keyY of a given 3ds allows for modification of DSiWare export contents, and chained with several other public vulns, ultimately arm9 execution.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.8.0-X&lt;br /&gt;
| December 2017&lt;br /&gt;
| January 2018&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| Improper validation of DSiWare title SRLs&lt;br /&gt;
| The 3DS does not verify if the actual SRL embedded in the title&#039;s directory matches the titleID in the TMD before launching it or importing it from an sd DSiWare export. &lt;br /&gt;
| This allows embedding older, exploitable DSiWare titles in completely different, unexploitable DSiWare titles. Since DSiWare has raw NAND RW, this can result in arm9 control through FIRM known-plaintext and sighax attacks.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.10.0-X&lt;br /&gt;
| 2015?&lt;br /&gt;
| December 2016&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| DSiWare import/export functions allow TWL system titles as arguments&lt;br /&gt;
| AM ImportTwlBackup/ExportTwlBackup unnecessarily allow TWL system titles such as DS Download Play to import/export from userland and System Settings -&amp;gt; Data Management (only am:sys is needed for userland). This is difficult to abuse for dsihax injection because no TWL system title has a save file, and any import with a save included will result in FS err C8804464. However, there is at least one dsihax primary that can load a payload from a non-NAND source, and not error if it can&#039;t access its public.sav (JPN Flipnote Studio v0).&lt;br /&gt;
| When combined with other public vulns, arm9 code execution.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.10.0-X&lt;br /&gt;
| May 2018&lt;br /&gt;
| Sept 2018&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| [[Gamecard_Services_PXI]] unchecked REG_CTRCARDCNT transfer-size&lt;br /&gt;
| The u8 REG_CTRCARDCNT transfer-size parameter for the [[Gamecard_Services_PXI]] read/write CTRCARD commands is used as an index for an array of u16 values. Before [[5.0.0-11|5.0.0-X]] this u8 value wasn&#039;t checked, thus out-of-bounds reads could be triggered(which is rather useless in this case).&lt;br /&gt;
| Out-of-bounds read for a value which gets written to a register.&lt;br /&gt;
| [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| 2013?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[PXI_Registers|PXI]] cmdbuf buffer overrun&lt;br /&gt;
| The Process9 code responsible [[PXI_Registers|PXI]] communications didn&#039;t verify the size of the incoming command before writing it to a C++ member variable. &lt;br /&gt;
| Probably ARM9 code execution&lt;br /&gt;
| [[5.0.0-11|5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| March 2015, original timeframe if any unknown&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]]/[[User:Yellows8|Yellows8]]/maybe others(?)&lt;br /&gt;
|-&lt;br /&gt;
| [[Application_Manager_Services_PXI|PXIAM]]:ImportCertificates (See also [[Application_Manager_Services|this]])&lt;br /&gt;
| When handling this command, Process9 allocates a 0x2800-byte heap buffer, then copies the 4 FCRAM input buffers to this heap buffer without checking the sizes at all(only the buffers with non-zero sizes are copied). Starting with [[5.0.0-11|5.0.0-X]], the total combined size of the input data must be &amp;lt;=0x2800.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| May 2013&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Process_Services_PXI|PS RSA]] commands buffer overflows&lt;br /&gt;
| pxips9 cmd1(not accessible via ps:ps) and VerifyRsaSha256: unchecked copy to a buffer in Process9&#039;s .bss, from the input FCRAM buffer. The buffer is located before the pxi cmdhandler threads&#039; stacks. SignRsaSha256 also has a buf overflow, but this isn&#039;t exploitable.&lt;br /&gt;
The buffer for this is the buffer for the signature data. With v5.0, the signature buffer was moved to stack, with a check for the signature data size. When the signature data size is too large, Process9 uses [[SVC|svcBreak]].&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[PXI_Registers|PXI]] pxi_id bad check&lt;br /&gt;
| The Process9 code responsible for [[PXI_Registers|PXI]] communications read pxi_id as a signed char. There were two flaws:&lt;br /&gt;
* They used it as index to a lookup-table without checking the value at all.&lt;br /&gt;
* Another function verified that pxi_id &amp;lt; 7, allowing negative values to pass the check. This would also cause an out-of-range table-lookup.&lt;br /&gt;
| Maybe ARM9 code execution&lt;br /&gt;
| [[3.0.0-5|3.0.0-5]]&lt;br /&gt;
|&lt;br /&gt;
| March 2015, originally 2012 for the first issue at least&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]], maybe others(?)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Kernel9 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[CONFIG Registers#CFG9_SYSPROT9|CFG9_SYSPROT9]] bit1 not set by Kernel9&lt;br /&gt;
| Old versions of Kernel9 never set bit1 of [[CONFIG Registers#CFG9_SYSPROT9|CFG9_SYSPROT9]]. This leaves the [[OTP Registers|0x10012000]]-region unprotected (this region should be locked early during boot!). Since it&#039;s never locked, you can dump it once you get ARM9 code execution.&lt;br /&gt;
&lt;br /&gt;
From [[3.0.0-5|3.0.0-X]] this was fixed by setting the bit in Kernel9 after poking some registers in that region. On New3DS arm9loader sets this bit instead of Kernel9, which is exploitable through a hardware + software vulnerability (see arm9loaderhax / description).&lt;br /&gt;
&lt;br /&gt;
This flaw resurged when it gained a new practical use: retrieving the OTP data for a New3DS console in order to decrypt the key data used in arm9loader (see enhanced-arm9loaderhax / description). This was performed by downgrading to a vulnerable system version. By accounting for differences in CTR-NAND crypto (0x05 -&amp;gt; 0x04, see partition encryption types [[Flash_Filesystem#NAND_structure|here]]) and using an Old3DS [[NCSD#NCSD_header|NCSD Header]], it is possible to boot a New3DS using Old3DS firmware 1.0-2.x to retrieve the required OTP data using this flaw.&lt;br /&gt;
| Dumping the [[OTP Registers|OTP]] area.&lt;br /&gt;
Decrypting New3DS sector 0x96 keyblock.&lt;br /&gt;
| [[3.0.0-5|3.0.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| February 2015&lt;br /&gt;
| [[User:Plutooo|plutoo]], Normmatt independently&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ARM11 software ==&lt;br /&gt;
=== Kernel11 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcUnbindInterrupt]] double free when irqId = 15&lt;br /&gt;
| svcBindInterrupt and svcUnbindInterrupt give special treatment to irqId 15 (FIQ helper): the access control list is bypassed and the provided KInterruptEvent (event or semaphore, via handle) is stored inside a singleton static object after having its refcount increased by 1.&lt;br /&gt;
&lt;br /&gt;
svcUnbindInterrupt assumes that the user-provided handle is what is stored in the singleton and will decref the user-provided KInterruptEvent twice, causing a use-after-free if the attacker didn&#039;t actually provide an handle to the same event or semaphore.&lt;br /&gt;
&lt;br /&gt;
This was &amp;quot;fixed&amp;quot; on [[11.14.0-46|11.14.0-X]] by preventing irqId 15 to be bound on retail units altogether (in both functions).&lt;br /&gt;
| Arm11 kernel code execution&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]] (only on retail units)&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2019&lt;br /&gt;
| [[User:TuxSH|TuxSH]], maybe others&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcKernelSetState]] op=3 could map the NULL page&lt;br /&gt;
| svcKernelSetState op=3 param1=1 maps the firmlaunch parameters page to the user-specified VA.&lt;br /&gt;
&lt;br /&gt;
It had previously no check, allowing the attacker to map data at VA 0.&lt;br /&gt;
&lt;br /&gt;
Starting from [[11.14.0-46|11.14.0-X]], the VA must be in the standard 0x10000000-0x14000000 address range.&lt;br /&gt;
| Mapping the NULL page (as RW) to leverage other kernel vulnerabilities&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2019&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcMapProcessMemory]] can map the NULL page&lt;br /&gt;
| svcMapProcessMemory&#039;s destination VA is unchecked.&lt;br /&gt;
&lt;br /&gt;
By passing a big enough &amp;quot;size&amp;quot; parameter, an attacker can map chunks of data at VA 0 in the destination (caller) process.&lt;br /&gt;
| Mapping the NULL page (as RW) to leverage other kernel vulnerabilities&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2020&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| Resource limit use-after-free&lt;br /&gt;
| When assigning a KResourceLimit to a KProcess, the reslimit&#039;s refcounter doesn&#039;t get incremented. This essentially means all KResourceLimit get freed if pm gets somehow terminated.&lt;br /&gt;
&lt;br /&gt;
It turns out it is possible to ask pm (via ns:s or pm:app) to terminate itself along all other KIPs simply by passing TID 0004000100001000.&lt;br /&gt;
&lt;br /&gt;
Calling [[SVC|svcGetResourceLimit]] afterwards triggers a use-after-free. This is rather difficult to exploit, however: there is one slot left in the reslimit slabheap. An attacker either has to map the NULL page as R(W)X (svcControlProcessMemory vuln fixed on [[11.8.0-41|11.8.0-X]]), or use one of the map-null exploits above while having access to svcCreateResourceLimit (with the only one that is easy enough to use in that context having been fixed on [[11.14.0-46|11.14.0-X]], anyway).&lt;br /&gt;
| Arm11 kernel code execution&lt;br /&gt;
| None (although near impossible to exploit on [[11.14.0-46|11.14.0-X]])&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2020&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcSetProcessIdealProcessor]] reference count overflow and therefore use-after-free.&lt;br /&gt;
| The SVC receive two arguments: handle and idealprocessor. The handle is used to get the KProcess object and the KProcess-&amp;gt;refCnt gets incremented,later the function check if the KProcess-&amp;gt;mem_type != BASE and if yes, it checks for idealprocessor == 2 or idealprocessor != 3. The problem here is that if you pass the idealprocessor = 3 it won&#039;t meet any condition and return the error 0xD9001BEA without decrement the reference count. &lt;br /&gt;
It can be abused to overflow the KProcess reference count that will lead to an Use-after-free. &lt;br /&gt;
| Before [[11.2.0-35|11.2.0-X]]: reference count overflow and therefore use-after-free.&lt;br /&gt;
| &lt;br /&gt;
| [[11.6.0-39|11.6.0-X]]&lt;br /&gt;
| November 2, 2017&lt;br /&gt;
| [[User:st4rk|st4rk]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcGetThreadList]] process reference leak&lt;br /&gt;
| When given a valid process handle (including &amp;lt;code&amp;gt;0xFFFF8001&amp;lt;/code&amp;gt;), svcGetThreadList forgets to decrement the reference count of the underlying [[KProcess]] instance, after having finished using it.&lt;br /&gt;
| Before [[11.2.0-35|11.2.0-X]]: reference count overflow and therefore use-after-free, but this UAF was most likely not exploitable&lt;br /&gt;
| &lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| April 3, 2017&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| kernelhax via gspwn&lt;br /&gt;
| Originally the kernel didn&#039;t initialize [[CONFIG11_Registers#CFG11_GPUPROT|CFG11_GPUPROT]]. Since it&#039;s 0 at hard-boot, this allowed the GPU to access the entire FCRAM + AXIWRAM.&lt;br /&gt;
| Entire FCRAM+AXIWRAM R/W.&lt;br /&gt;
| [[3.0.0-5|3.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| February 7, 2017&lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]] partly&lt;br /&gt;
|-&lt;br /&gt;
| fasthax&lt;br /&gt;
| When a KTimer is created in pulse mode, the kernel calls a virtual function to reset the timer each time it pulses. The scheduler is locked for that core to avoid race conditions, but another core can call CloseHandle on the timer and free it, leading to a UAF vtable call.&lt;br /&gt;
| See description.&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| May 2016&lt;br /&gt;
| nedwill&lt;br /&gt;
|-&lt;br /&gt;
| ipctakeover&lt;br /&gt;
| When sending cmdreplies, it does not validate that the src_addr and src_size match the equivalent dst_addr and dst_size. With a modified addr/size specified in a cmdreply for an output buffer, the data-copy for the first/last pages could be used to overwrite data outside of the buffer specified by the original process.&lt;br /&gt;
&lt;br /&gt;
Used by ctr-httpwn as of v1.2, for &amp;quot;ipctakeover/bosshaxx&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This can be used to takeover processes where the process is using your service session. Like HTTPC -&amp;gt; BOSS, for bosshaxx above. NIM takeover can be done too(actual stack buffer overflow can trigger), etc.&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| November 26, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Using IPC input buffers as output buffers&lt;br /&gt;
| When sending cmdreplies, it does not validate that the cmdreply descriptor type matches the equivalent cmdreq descriptor type. This could be used by an exploited sysmodule to use what was intended as an input-buffer as an output-buffer, and also combine other IPC vuln(s) with this.&lt;br /&gt;
&lt;br /&gt;
Used by ctr-httpwn as of v1.2, for &amp;quot;ipctakeover/bosshaxx&amp;quot;.&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| November 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
|  [[SVC]] table too small&lt;br /&gt;
|  The table of function pointers for SVC&#039;s only contains entries up to 0x7D, but the biggest allowed SVC for the table is 0x7F. Thus, executing SVC7E or SVC7F would make the SVC-handler read after the buffer, and interpret some ARM instructions as function pointers.&lt;br /&gt;
&lt;br /&gt;
However, this would require patching the kernel .text or modifying SVC-access-control. Even if you could get these to execute, they would still jump to memory that isn&#039;t mapped as executable.&lt;br /&gt;
| &lt;br /&gt;
|  None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| 2012&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
|  [[SVC|svcBackdoor (0x7B)]]&lt;br /&gt;
|  This backdoor allows executing SVC-mode code at the user-specified code-address. This is used by Process9, using this on the ARM11 (with NATIVE_FIRM) required patching the kernel .text or modifying SVC-access-control.&lt;br /&gt;
| See description&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]] (deleted)&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| veryslowpidhax&lt;br /&gt;
| &#039;&#039;&#039;This is completely different from the kernelmode-code-execution vuln described in the below separate entry.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
When updating the kernel global PID counter under [[SVC|svcCreateProcess]] the kernel does not check for wraparound to 0x0(the PID for the very first process). This only matters because [[Services|SM-module]] allows processes with PID value less than &amp;lt;total ARM11 FIRM modules&amp;gt; to access &#039;&#039;all&#039;&#039; services, without checking exheader service-access-control; and because Kernel11 checks for the PID to be 1 (loader) to use the input mem-region value on ControlMemory. This alone does not affect access the [[SVC|SVCs]] access table at all.&lt;br /&gt;
&lt;br /&gt;
Inlined ldrex+strex code is used for updating the above counter. [[11.2.0-35|11.2.0-X]] had changes for similar code, but it was only for dedicated ldrex+strex functions(mainly for kernel objects) and hence this PID code was not affected.&lt;br /&gt;
&lt;br /&gt;
With launching+terminating a sysmodule repeatedly with this via ns:s, it would take weeks to finish(if not at least about a month?).&lt;br /&gt;
| Access to all [[Services_API|services]], ControlMemory on any given mem-region.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| 2012 maybe?&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
|  slowhax/waithax&lt;br /&gt;
|  svcWaitSynchronizationN does not decrement the references to valid handles in an array before returning an error when it encounters an invalid handle. This allows one to (slowly) overflow the reference count for a handle object to zero.&lt;br /&gt;
| ARM11 kernel-mode code execution&lt;br /&gt;
| [[11.2.0-35|11.2.0-X]]&lt;br /&gt;
| [[11.2.0-35|11.2.0-X]]&lt;br /&gt;
| 2016&lt;br /&gt;
| nedwill, [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Memory_layout#ARM11_Detailed_virtual_memory_map|0xEFF00000]] / 0xDFF00000 ARM11 kernel virtual-memory&lt;br /&gt;
| The ARM11 kernel-mode 0xEFF00000/0xDFF00000 virtual-memory(size 0x100000) is mapped to phys-mem 0x1FF00000(entire DSP-mem + entire AXIWRAM), with permissions RW-. This is used during ARM11 kernel startup for loading the FIRM-modules from the FIRM section located in DSP-mem, this never seems to be used after that, however. This is never unmapped either.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| memchunkhax2.1&lt;br /&gt;
| Nintendo&#039;s fix for memchunkhax2 in [[10.4.0-29|10.4.0-X]] did not fix the GPU case: one may cause the requisite ToCToU race using gspwn, bypassing the new validation.&lt;br /&gt;
derrek&#039;s original 32c3 presentation for memchunkhax2 commented that a GPU-based attack was possible, but would be difficult.  However, memchunkhax2.1 showed that it was possible to do fairly reliably.&lt;br /&gt;
| ARM11 kernel code execution&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]], via the new [[Memory_Management#MemoryBlockHeader|memchunkhdr]] MAC which prevents modifying memchunkhdr data with DMA.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| [[User:Derrek|derrek]], aliaspider&lt;br /&gt;
|-&lt;br /&gt;
| memchunkhax2&lt;br /&gt;
| When allocating a block of memory, the &amp;quot;next&amp;quot; pointer of the [[Memory_Management#MemoryBlockHeader|memchunkhdr]] is accessed without being checked after being mapped to userland.&lt;br /&gt;
This allows a race condition, where the process can change the next pointer just before it&#039;s accessed. By pointing the next pointer to a crafted memchunckhdr in the kernel SlabHeap, some of the SlabHeap is allocated to the calling process, allowing to change vtables of kernel objects. &lt;br /&gt;
| ARM11 kernel code execution&lt;br /&gt;
| [[10.4.0-29|10.4.0-X]] (partially, see memchunkhax2.1)&lt;br /&gt;
| [[10.4.0-29|10.4.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| heaphax&lt;br /&gt;
| Can change the size of free memchunk structures stored in FCRAM using DMA, which leads to the ability to allocate memory chunks over already-allocated memory. This can be used in the SYSTEM region to allocate RW memory over any part of the NS system module, which is enough to take it over.&lt;br /&gt;
| Code execution with access to all of NS&#039;s privileges. (including downgrading) Code execution within any applet.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]], via the new [[Memory_Management#MemoryBlockHeader|memchunkhdr]] MAC which prevents modifying memchunkhdr data with DMA.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]]&lt;br /&gt;
| April 2015 ?&lt;br /&gt;
| smea&lt;br /&gt;
|-&lt;br /&gt;
| snshax&lt;br /&gt;
| Can force creation of Safe NS process into gspwn-able memory, allowing for takeover.&lt;br /&gt;
| Code execution with access to all of NS&#039;s privileges. (including downgrading)&lt;br /&gt;
| [[10.1.0-27|10.1.0-X]]&lt;br /&gt;
| [[10.1.0-27|10.1.0-X]]&lt;br /&gt;
| April 2015 ?&lt;br /&gt;
| smea&lt;br /&gt;
|-&lt;br /&gt;
|  AffinityMask/processorid validation&lt;br /&gt;
|  With [[10.0.0-27|10.0.0-X]] the following functions were updated: svcGetThreadAffinityMask, svcGetProcessAffinityMask, svcSetProcessAffinityMask, and svcCreateThread. The code changes for all but svcCreateThread are identical.&lt;br /&gt;
The original code with the first 3 did the following: &lt;br /&gt;
* if(u32_processorcount &amp;gt; ~0x80000001)return 0xe0e01bfd;&lt;br /&gt;
* if(s32_processorcount &amp;gt; &amp;lt;total_cores&amp;gt;)return 0xd8e007fd;&lt;br /&gt;
The following code replaced the above:&lt;br /&gt;
* if(u32_processorcount &amp;gt;= &amp;lt;total_cores+1&amp;gt;)return 0xd8e007fd;&lt;br /&gt;
In theory the latter should catch everything that the former did, so it&#039;s unknown if this was really a security issue.&lt;br /&gt;
&lt;br /&gt;
The svcCreateThread changes with [[10.0.0-27|10.0.0-X]] definitely did fix a security issue.&lt;br /&gt;
* Original code: &amp;quot;if(s32_processorid &amp;gt; &amp;lt;total_cores&amp;gt;)return 0xd8e007fd;&amp;quot;&lt;br /&gt;
* New code: &amp;quot;if(s32_processorid &amp;gt;= &amp;lt;total_cores&amp;gt; || s32_processorid &amp;lt;= -4)return 0xd8e007fd;&amp;quot;&lt;br /&gt;
This fixed an off-by-one issue: if one would use processorid=total_cores, which isn&#039;t actually a valid value, svcCreateThread would accept that value on &amp;lt;[[10.0.0-27|10.0.0-X]]. This results in data being written out-of-bounds(baseaddr = arrayaddr + entrysize*processorid), which has the following result:&lt;br /&gt;
* Old3DS: Useless kernel-mode crash due to accessing unmapped memory.&lt;br /&gt;
* New3DS: uncontrolled data write into a kernel-mode L1 MMU-table. This isn&#039;t really useful: the data can&#039;t be controlled, and the data which gets overwritten is all-zero anyway(this isn&#039;t anywhere near MMU L1 entries for actually mapped memory).&lt;br /&gt;
The previous version also allowed large negative s32_processorid values(negative processorid values are special values not actual procids), but it appears using values like that won&#039;t actually do anything(meaning no crash) besides the thread not running / thread not running for a while(besides triggering a kernelpanic with certain s32_processorid value(s)).&lt;br /&gt;
| Nothing useful&lt;br /&gt;
|  [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| svcCreateThread issue: May 31, 2015. The rest: September 8, 2015, via v9.6-&amp;gt;v10.0 ARM11-kernel code-diff.&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| memchunkhax&lt;br /&gt;
| The kernel originally did not validate the data stored in the FCRAM kernel heap [[Memchunkhdr|memchunk-headers]] for free-memory at all. Exploiting this requires raw R/W access to these memchunk-headers, like physical-memory access with gspwn.&lt;br /&gt;
&lt;br /&gt;
There are &#039;&#039;multiple&#039;&#039; ways to exploit this, but the end-result for most of these is the same: overwrite code in AXIWRAM via the 0xEFF00000/0xDFF00000 kernel virtual-memory mapping.&lt;br /&gt;
&lt;br /&gt;
This was fixed in [[9.3.0-21|9.3.0-X]] by checking that the memchunk(including size, next, and prev ptrs) is located within the currently used heap memory. The kernel may also check that the next/prev ptrs are valid compared to other memchunk-headers basically. When any of these checks fail, kernelpanic() is called.&lt;br /&gt;
| When combined with other flaws: ARM11-kernelmode code execution&lt;br /&gt;
| [[9.3.0-21|9.3.0-21]]&lt;br /&gt;
| &lt;br /&gt;
| February 2014&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Multiple [[KLinkedListNode|KLinkedListNode]] SlabHeap use after free bugs&lt;br /&gt;
| The ARM11-kernel did access the &#039;key&#039; field of [[KLinkedListNode|KLinkedListNode]] objects, which are located on the SlabHeap, after freeing them. Thus, triggering an allocation of a new [[KLinkedListNode|KLinkedListNode]] object at the right time could result in a type-confusion. Pseudo-code:&lt;br /&gt;
SlabHeap_free(KLinkedListNode);&lt;br /&gt;
KObject *obj = KLinkedListNode-&amp;gt;key;  // the object there might have changed!&lt;br /&gt;
This bug appeared all over the place.&lt;br /&gt;
| ARM11-kernelmode code exec maybe&lt;br /&gt;
| [[8.0.0-18|8.0.0-18]]&lt;br /&gt;
| &lt;br /&gt;
| April 2015&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| PXI [[RPC_Command_Structure|Command]] input/output buffer permissions&lt;br /&gt;
| Originally the ARM11-kernel didn&#039;t check permissions for PXI input/output buffers for commands. Starting with [[6.0.0-11|6.0.0]] PXI input/output buffers must have RW permissions, otherwise kernelpanic is triggered.&lt;br /&gt;
| &lt;br /&gt;
| [[6.0.0-11|6.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcStartInterProcessDma]]&lt;br /&gt;
| For svcStartInterProcessDma, the kernel code had the following flaws:&lt;br /&gt;
&lt;br /&gt;
* Originally the ARM11-kernel read the input DmaConfig structure directly in kernel-mode(ldr(b/h) instructions), without checking whether the DmaConfig address is readable under userland. This was fixed by copying that structure to the SVC-mode stack, using the ldrbt instruction.&lt;br /&gt;
&lt;br /&gt;
* Integer overflows for srcaddr+size and dstaddr+size are now checked(with [[6.0.0-11]]), which were not checked before.&lt;br /&gt;
&lt;br /&gt;
* The kernel now also checks whether the srcaddr/dstaddr (+size) is within userland memory (0x20000000), the kernel now (with [[6.0.0-11]]) returns an error when the address is beyond userland memory. Using an address &amp;gt;=0x20000000 would result in the kernel reading from the process L1 MMU table, beyond the memory allocated for that MMU table(for vaddr-&amp;gt;physaddr conversion). &lt;br /&gt;
| &lt;br /&gt;
| [[6.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| DmaConfig issue: unknown. The rest: 2014&lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]] independently&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcControlMemory]] Parameter checks&lt;br /&gt;
| For svcControlMemory the parameter check had these two flaws:&lt;br /&gt;
&lt;br /&gt;
* The allowed range for addr0, addr1, size parameters depends on which MemoryOperation is being specified. The limitation for GSP heap was only checked if op=(u32)0x10003. By setting a random bit in op that has no meaning (like bit17?), op would instead be (u32)0x30003, and the range-check would be less strict and not accurate. However, the kernel doesn&#039;t actually use the input address for LINEAR memory-mapping at all besides the range-checks, so this isn&#039;t actually useful. This was fixed in the kernel by just checking for the LINEAR bit, instead of comparing the entire MemoryOperation value with 0x10003.&lt;br /&gt;
&lt;br /&gt;
* Integer overflows on (addr0+size) are now checked that previously weren&#039;t (this also applies to most other address checks elsewhere in the kernel).&lt;br /&gt;
&lt;br /&gt;
| &lt;br /&gt;
| [[5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| [[RPC_Command_Structure|Command]] request/response buffer overflow&lt;br /&gt;
| Originally the kernel did not check the word-values from the command-header. Starting with [[5.0.0-11]], the kernel will trigger a kernelpanic() when the total word-size of the entire command(including the cmd-header) is larger than 0x40-words (0x100-bytes). This allows overwriting threadlocalstorage+0x180 in the destination thread. However, since the data written there would be translate parameters (such as header-words + buffer addresses), exploiting this would likely be very difficult, if possible at all.&lt;br /&gt;
&lt;br /&gt;
If the two words at threadlocalstorage+0x180 could be overwritten with controlled data this way, one could then use a command with a buffer-header of &amp;lt;nowiki&amp;gt;((size&amp;lt;&amp;lt;14) | 2)&amp;lt;/nowiki&amp;gt; to write arbitrary memory to any RW userland memory in the destination process.&lt;br /&gt;
| &lt;br /&gt;
| [[5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| v4.1 FIRM -&amp;gt; v5.0 code diff&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|SVC stack allocation overflows]]&lt;br /&gt;
| &lt;br /&gt;
* Syscalls that allocate a variable-length array on stack, only checked bit31 before multiplying by 4/16 (when calculating how much memory to allocate). If a large integer was passed as input to one of these syscalls, an integer overflow would occur, and too little memory would have been allocated on stack resulting in a buffer overrun. &lt;br /&gt;
* The alignment (size+7)&amp;amp;~7 calculation before allocation was not checked for integer overflow.&lt;br /&gt;
&lt;br /&gt;
This might allow for ARM11 kernel code-execution.&lt;br /&gt;
&lt;br /&gt;
(Applies to svcSetResourceLimitValues, svcGetThreadList, svcGetProcessList, svcReplyAndReceive, svcWaitSynchronizationN.)&lt;br /&gt;
| &lt;br /&gt;
| [[5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| v4.1 FIRM -&amp;gt; v5.0 code diff&lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]] complementary&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcControlMemory]] MemoryOperation MAP memory-permissions&lt;br /&gt;
| svcControlMemory with MemoryOperation=MAP allows mapping the already-mapped process virtual-mem at addr1, to addr0. The lowest address permitted for addr1 is 0x00100000. Originally the ARM11 kernel didn&#039;t check memory permissions for addr1. Therefore .text as addr1 could be mapped elsewhere as RW- memory, which allowed ARM11 userland code-execution.&lt;br /&gt;
| &lt;br /&gt;
| [[4.1.0-8]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[RPC_Command_Structure|Command]] input/output buffer permissions&lt;br /&gt;
| Originally the ARM11 kernel didn&#039;t check memory permissions for the input/output buffers for commands. Starting with [[4.0.0-7]] the ARM11 kernel will trigger a kernelpanic() if the input/output buffers don&#039;t have the required memory permissions. For example, this allowed a FSUSER file-read to .text, which therefore allowed ARM11-userland code execution.&lt;br /&gt;
| &lt;br /&gt;
| [[4.0.0-7]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcReadProcessMemory/svcWriteProcessMemory memory]] permissions&lt;br /&gt;
| Originally the kernel only checked the first page(0x1000-bytes) of the src/dst buffers, for svcReadProcessMemory and svcWriteProcessMemory. There is no known retail processes which have access to these SVCs.&lt;br /&gt;
| &lt;br /&gt;
| [[4.0.0-7]]&lt;br /&gt;
| &lt;br /&gt;
| 2012?&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== [[FIRM]] Sysmodules ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[Services|&amp;quot;srv:pm&amp;quot;]] process registration&lt;br /&gt;
| Originally any process had access to the port &amp;quot;srv:pm&amp;quot;. The PID&#039;s used for the (un)registration commands are not checked either. This allowed any process to re-register itself with &amp;quot;srv:pm&amp;quot;, and therefore allowed the process to give itself access to any service, bypassing the exheader service-access-control list.&lt;br /&gt;
&lt;br /&gt;
This was fixed in [[7.0.0-13]]: starting with [[7.0.0-13]] &amp;quot;srv:pm&amp;quot; is now a service instead of a globally accessible port. Only processes with PID&#039;s less than 6 (in other words: fs, ldr, sm, pm, pxi modules) have access to it. With [[7.0.0-13]] there can only be one session for &amp;quot;srv:pm&amp;quot; open at a time(this is used by pm module), svcBreak will be executed if more sessions are opened by the processes which can access this.&lt;br /&gt;
&lt;br /&gt;
This flaw was needed for exploiting the &amp;lt;=v4.x Process9 PXI vulnerabilities from ARM11 userland ROP, since most applications don&#039;t have access to those service(s).&lt;br /&gt;
| Access to arbitrary services&lt;br /&gt;
| [[7.0.0-13]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| FSDIR null-deref&lt;br /&gt;
| [[Filesystem_services|FS]]-module may crash in some cases when handling directory reading. The trigger seems to be due to using [[FSDir:Close]] without closing the dir-handle afterwards?(Perhaps this is caused by out-of-memory?) This seems to be useless since it&#039;s just a null-deref.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| May 19(?)-20, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Useless [[SM]] off-by-one write&lt;br /&gt;
| After accepting a new session, [[SM]] writes a (handler ID (0 for srv: sessions (max. 64), 1 for the srv:pm one), pointer to session context structure in BSS) pair in a global array. However that array is only 64-entry-big instead of 65 (as it ought to be), and no bound check is done in that regard.&lt;br /&gt;
&lt;br /&gt;
Unfortunately, as of [[11.4.0-37]], the overwritten fields are totally unused after their initialization by &amp;lt;code&amp;gt;__libc_init_array&amp;lt;/code&amp;gt;.&lt;br /&gt;
| Not currently exploitable&lt;br /&gt;
| None&lt;br /&gt;
| [[11.4.0-37]]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| smpwn&lt;br /&gt;
| When registering a new service (or &amp;quot;port&amp;quot;), no bound checks are done on the service table. One can simply call RegisterPort repeatedly to overflow that table: it will overflow into the command replay structure.&lt;br /&gt;
&lt;br /&gt;
Combined with a other minor bugs in the sysmodule, it is possible to take over [[SM]] with this nevertheless difficult-to-exploit vulnerability.&lt;br /&gt;
| Code execution under [[SM]], etc.&lt;br /&gt;
| [[11.16.0-48]]&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| July 2017&lt;br /&gt;
| [[User:TuxSH|TuxSH]] (independently), presumably ichfly before &lt;br /&gt;
|-&lt;br /&gt;
| PXI cmdbuf buffer overrun &lt;br /&gt;
| Like its Arm9 counterpart, before version [[5.0.0-11|5.0.0-X]], the PXI system module did not check the command sizes. This makes it possible to get ROP under the PXI sysmodule from a pwned Process9.&lt;br /&gt;
safecerthax uses it to takeover the Arm11 processor after directly getting remote code execution on the Arm9 side. Though, is useless in classic Arm11 -&amp;gt; Arm9 chains.&lt;br /&gt;
| ROP under [[PXI_Services|PXI]]&lt;br /&gt;
| probably [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| &lt;br /&gt;
| Everyone&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Standalone Sysmodules ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in system-module system-version&lt;br /&gt;
!  Last system-module system-version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Timeframe this was added to wiki&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[CSND_Services|CSND]] sysmodule crash due to out of bounds parameters.&lt;br /&gt;
| The CSND command [[CSND:PlaySoundDirectly|PlaySoundDirectly (0x00040080)]] takes a channel ID as the first parameter. Any value outside the range [0-3] makes the system module become unstable or crash due to an out of bounds memory read. &lt;br /&gt;
| Out of bounds memory read, probably not exploitable. More research needed.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| January 2021&lt;br /&gt;
| January 22, 2021&lt;br /&gt;
| [[User:PabloMK7|PabloMK7]]&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| SSLoth: [[SSL_Services|SSL]] sysmodule improper certificate verification&lt;br /&gt;
| Initially, the SSL sysmodule missed the R_VERIFY_RES_SIGNATURE entry in the &amp;quot;resource list&amp;quot; provided to the RSA BSAFE library. Consequently, it did not check signatures when validating certificate chains. &lt;br /&gt;
| Forge fake certificates, spoof official servers and perform MitM attacks on SSL/TLS connections.&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| 2020&lt;br /&gt;
| December 18, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]], shutterbug2000 (independently)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD:ndm]] SetNZoneMacFilter (cmd8) stack smashing&lt;br /&gt;
| The length of the mac filter is not checked before being copied to a fixed-size buffer on stack.&lt;br /&gt;
| ROP under [[CECD_Services|CECD]] sysmodule&lt;br /&gt;
| None&lt;br /&gt;
| [[11.13.0-45]]&lt;br /&gt;
| 2020&lt;br /&gt;
| July 20, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] message box access &lt;br /&gt;
| CECD allows any process to write to any message box, thus allowing to write Streetpass data to the message box of any title.&lt;br /&gt;
| Install exploit for any title having a vulnerability in Streetpass data parsers (see CTRSDK Streetpass parser vulnerability).&lt;br /&gt;
| None&lt;br /&gt;
| None&lt;br /&gt;
| ?&lt;br /&gt;
| June 1, 2020&lt;br /&gt;
| Everyone?&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] packet type 0x32/0x34 stack-smashing&lt;br /&gt;
| When parsing Streetpass packets of type 0x32 and 0x34, CECD copies a list without checking the number of entries. The packet length is limited to 0x400 bytes, which is not enough to reach the end of the stack frame and overwrite the return address. However, the buffer located just next to the packet buffer is actually filled with data sent just before, hence actually allowing to overwrite the whole stack frame with conrolled data.&lt;br /&gt;
| RCE under [[CECD_Services|CECD]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| Summer 2019&lt;br /&gt;
| June 1, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] TMP files parser multiple vulnerabilities&lt;br /&gt;
| When parsing &amp;quot;TMP_XXX&amp;quot; files, CECD does not check the number of messages contained in the file. This allows to overflow the array of message pointers and message sizes on the stack. Pointers aren&#039;t controlled and sizes are limited (one cannot send gigabytes of data...), yet the last message size can be an arbitrary value (the current message pointer goes outside the file buffer and the parsing loop is broken). This allows to overwrite a pointer to a lock object on the stack and decrement an arbitrary value in memory. One can change the TMP file parsing mode to have CECD trying to free all the message buffers after parsing the next TMP file. The parsing mode is usually restored when parsing a new TMP file, but an invalid TMP file allows to make a function returns an error before the mode is restored , the return value is not checked and the parser consider the file valid. The message pointers and sizes arrays are not updated though, this is not a problem since the previous TMP file buffer is reused for the new TMP file in memory. Thus the message pointers actually points to controlled data. This allows to get a bunch of fake heap chunk freed, thus a bunch of unsafe unlink arbitrary writes.&lt;br /&gt;
| RCE under [[CECD_Services|CECD]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| Summer 2019&lt;br /&gt;
| June 1, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Config_Services|CFG]]:CreateConfigInfoBlk integer underflow&lt;br /&gt;
| When creating a new block it checks the size of the block is &amp;lt;= 0x8000, but it doesn&#039;t check that the block size is less than the remaining space. This induces an integer underflow (remaining_space-block_size), the result is then used for another check (buf_start+current_offset+constant &amp;lt;= remaining_space-block_size) and then in a mempcy call (dest = buf_start+(u16)(remaining_space-block_size), size =block_size). This allow for writing past the buffer, however because of the u16 cast in the memcpy call memory has to be mapped from buf_start to buf_start+0x10000 (cannot write backward).&lt;br /&gt;
| Theoritically ROP under CFG services, but BSS section is to small (size &amp;lt;= 0x10000) so it only results in a crash.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.8.0-41]]&lt;br /&gt;
| November, 2018&lt;br /&gt;
| November 24, 2018&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[MP:SendDataFrame]] missing input array index validation&lt;br /&gt;
| [[MP:SendDataFrame]] doesn&#039;t validate the input index at cmdreq[1], unless the function for flag=non-zero is executed. This is used to calculate the following, without validating the index at all: someptr = stateptr + (index*0x924) + somestateoffset.&lt;br /&gt;
&lt;br /&gt;
After validating some flags from someptr, when input_flag=0 the input buffer data is copied to someptr+someotheroffset+0x14 with the u16 size loaded from someptr+someotheroffset.&lt;br /&gt;
&lt;br /&gt;
With a large input index someptr could be setup to be at a &amp;lt;target address&amp;gt;, for overwriting memory.&lt;br /&gt;
&lt;br /&gt;
This is probably difficult to exploit.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[8.0.0-18]](MP-sysmodule v2048)&lt;br /&gt;
| January 22, 2017&lt;br /&gt;
| January 22, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[MP_Services|MP]] cmd1 out-of-bounds handle read&lt;br /&gt;
| MP-sysmodule handles the input parameter for cmd1 as a s32. It checks for &amp;gt;=16, but not &amp;lt;0. With &amp;lt;16 it basically does the following(array of entries 4-bytes each): *outhandle = ((Handle*)(stateptr+offsetinstate))[inputindex].&lt;br /&gt;
&lt;br /&gt;
Hence, this can be used to load any handle in MP-sysmodule memory. MP doesn&#039;t really have any service handles of interest however(can be obtained from elsewhere too).&lt;br /&gt;
| Reading any handle in MP-sysmodule memory.&lt;br /&gt;
| None&lt;br /&gt;
| [[8.0.0-18]](MP-sysmodule v2048)&lt;br /&gt;
| January 21, 2017&lt;br /&gt;
| January 22, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| AM stack/.bss infoleak via [[AM:ReadTwlBackupInfo]]([[AM:ReadTwlBackupInfoEx|Ex]])&lt;br /&gt;
| After writing the output-info structure to stack, it then copies that structure to the output buffer ptr using the size from the command. The size is not checked. This could be used to read data from the AM-service-thread stack handling the command + .bss.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;This was not tested on hardware.&#039;&#039;&#039;&lt;br /&gt;
| Stack/.bss reading&lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27]](AM v9217)&lt;br /&gt;
| Roughly October 17, 2016&lt;br /&gt;
| October 25, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| AM module APcert infoleak via 00000000.ctx files&lt;br /&gt;
| Just after a download title is purchased from the eShop, the .ctx is in an initialized state of all FFs past the header. During download, the FF area is filled with the console APcert. Thus, it is possible to create a xorpad from the initial state and use it to decrypt the APcert filled state.&lt;br /&gt;
| APcert contains the deviceID, which can beneficial in decrypting the movable.sed (since deviceID is mathmatically related to the LFCS).&lt;br /&gt;
| None&lt;br /&gt;
| [[11.16.0-49]]&lt;br /&gt;
| August, 2022&lt;br /&gt;
| March 17, 2023&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| [[MVD_Services|MVD]]: Stack buffer overflow with [[MVDSTD:SetupOutputBuffers]].&lt;br /&gt;
| The input total_entries is not validated when initially processing the input entry-list. This fixed-size input entry-list is copied to stack from the command request. The loop for processing this initializes a global table, the converted linearmem-&amp;gt;physaddrs used there are also copied to stack(0x8-bytes of physaddrs per entry).&lt;br /&gt;
&lt;br /&gt;
If total_entries is too large, MVD-sysmodule will crash due to reading unmapped memory following the stack(0x10000000). Afterwards if the out-of-bounds total_entries is smaller than that, it will crash due accessing address 0x0, hence this useless.&lt;br /&gt;
| MVD-sysmodule crash.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.0.0-20]]&lt;br /&gt;
| April 22, 2016 (Tested on the 25th)&lt;br /&gt;
| April 25, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWM_Services|NWM]]: Using CTRSDK heap with UDS sharedmem from the user-process.&lt;br /&gt;
| See the HTTP-sysmodule section below.&lt;br /&gt;
&lt;br /&gt;
CTRSDK heap is used with the sharedmem from [[NWMUDS:InitializeWithVersion]]. Buffers are allocated/freed under this heap using [[NWMUDS:Bind]] and [[NWMUDS:Unbind]].&lt;br /&gt;
&lt;br /&gt;
Hence, overwriting sharedmem with gspwn then using [[NWMUDS:Unbind]] results in the usual controlled CTRSDK memchunk-header write, similar to HTTP-sysmodule.&lt;br /&gt;
&lt;br /&gt;
This could be done by creating an UDS network, without any other nodes on the network.&lt;br /&gt;
&lt;br /&gt;
Besides CTRSDK memchunk-headers, there are no addresses stored under this sharedmem.&lt;br /&gt;
| ROP under NWM-module.&lt;br /&gt;
| None (need to check, but CTRSDK heap code is vulnerable)&lt;br /&gt;
| [[9.0.0-20|9.0.0-X]]&lt;br /&gt;
| April 10, 2016&lt;br /&gt;
| April 16, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[DLP_Services|DLP]]: Out-of-bounds memory access during spectator [[Download_Play|data-frame]] checksum calculation&lt;br /&gt;
| DLP doesn&#039;t validate the frame_size when receiving spectator data-frames at all, unlike non-spectator data-frames. The actual spectator data-frame parsing code doesn&#039;t use that field either. However, the data-frame checksum calculation code called during checksum verification does use the frame_size for loading the size of the framebuf.&lt;br /&gt;
&lt;br /&gt;
Hence, using a large frame_size like 0xFFFF will result in the checksum calculation code reading data out-of-bounds. This isn&#039;t really useful, you could trigger a remote local-WLAN DLP-sysmodule crash while a 3DS system is scanning for DLP networks(due to accessing unmapped memory), but that&#039;s about all(trying to infoleak with this likely isn&#039;t useful either).&lt;br /&gt;
| DLP-sysmodule crash, handled by dlplay system-application by a &amp;quot;connection interrupted&amp;quot; error eventually then a fatal-error via ErrDisp.&lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| April 8, 2016 (Tested on the 10th)&lt;br /&gt;
| April 10, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[DLP_Services|DLP]]: Out-of-bounds output data writing during spectator sysupdate titlelist [[Download_Play|data-frame]] handling&lt;br /&gt;
| The total_entries and out_entryindex fields for the titlelist DLP spectator data-frames are not validated. This is parsed during DLP network scanning. Hence, the specified titlelist data can be written out-of-bounds using the specified out_entryindex and total_entries. A crash will occur while reading the input data-frame titlelist if total_entries is larger than 0x27A, due to accessing unmapped memory.&lt;br /&gt;
&lt;br /&gt;
There&#039;s not much non-zero data to overwrite following the output buffer(located in sharedmem), any ptrs are located in sharedmem. Overwriting certain ptr(s) are only known to cause a crash when attempting to use the DLP-client shutdown service-command.&lt;br /&gt;
&lt;br /&gt;
There&#039;s no known way to exploit the above crash, since the linked-list code involves writes zeros(with a controlled start ptr).&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| April 8-9, 2016&lt;br /&gt;
| April 10, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[IR_Services|IR]]: Stack buffer overflow with custom hardware&lt;br /&gt;
| Originally IR sysmodule used the read value from the I2C-IR registers TXLVL and RXLVL without validating them at all. See [[10.6.0-31|here]] for the fix. This is the size used for reading the data-recv FIFO, etc. The output buffer for reading is located on the stack.&lt;br /&gt;
&lt;br /&gt;
This should be exploitable if one could successfully setup the custom hardware for this and if the entire intended sizes actually get read from I2C.&lt;br /&gt;
| ROP under IR sysmodule.&lt;br /&gt;
| [[10.6.0-31|10.6.0-31]]&lt;br /&gt;
| &lt;br /&gt;
| February 23, 2016 (Unknown if it was noticed before then)&lt;br /&gt;
| February 23, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[HTTP_Services|HTTP]]: Using CTRSDK heap with sharedmem from the user-process.&lt;br /&gt;
| The data from httpcAddPostDataAscii and other commands is stored under a CTRSDK heap. That heap is the sharedmem specified by the user-process via the HTTPC Initialize command.&lt;br /&gt;
Normally this sharedmem isn&#039;t accessible to the user-process once the sysmodule maps it, hence using it is supposed to be &amp;quot;safe&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This isn&#039;t the case due to gspwn however. Since CTRSDK heap code is so insecure in general, one can use gspwn to locate the HTTPC sharedmem + read/write it, then trigger a mem-write under the sysmodule. This can then be used to get ROP going under HTTP-sysmodule.&lt;br /&gt;
&lt;br /&gt;
This is exploited by [https://github.com/yellows8/ctr-httpwn/ctr-httpwn ctr-httpwn].&lt;br /&gt;
| ROP under HTTP sysmdule.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.13.0-45|11.13.0-X]]&lt;br /&gt;
| Late 2015&lt;br /&gt;
| March 22, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NIM_Services|NIM]]: Downloading old title-versions from eShop&lt;br /&gt;
| Multiple NIM service commands(such as [[NIMS:StartDownload]]) use a title-version value specified by the user-process, NIM does not validate that this input version matches the latest version available via SOAP. Therefore, when combined with AM(PXI) [[#Process9|title-downgrading]] via deleting the target eShop title with System Settings Data Management(if the title was already installed), this allows downloading+installing any title-version from eShop &#039;&#039;if&#039;&#039; it&#039;s still available from CDN.&lt;br /&gt;
The easiest way to exploit this is to just patch the eShop system-application code using these NIM commands(ideally the code which loads the title-version).&lt;br /&gt;
&lt;br /&gt;
Originally this was tested with a debugging-system via modded-FIRM, eventually smea implemented it in HANS for the 32c3 release.&lt;br /&gt;
| Downloading old title-versions from eShop&lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| October 24, 2015 (Unknown when exactly the first eShop title downgrade was actually tested, maybe November)&lt;br /&gt;
| January 7, 2016 (Same day Ironfall v1.0 was removed from CDN via the main-CXI files)&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SPI_Services|SPI]] service out-of-bounds write&lt;br /&gt;
| cmd1 has out-of-bounds write allowing overwrite of some static variables in .data.&lt;br /&gt;
| Code execution under spi sysmodule; access to [[CONFIG11_Registers|CFG11_GPUPROT]] and ultimately kernel code execution. &lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NFC_Services|NFC]] module service command buf-overflows&lt;br /&gt;
| NFC module copies data with certain commands, from command input buffers to stack without checking the size. These commands include the following, it&#039;s unknown if there&#039;s more commands with similar issues: &amp;quot;nfc:dev&amp;quot; &amp;lt;0x000C....&amp;gt; and &amp;quot;nfc:s&amp;quot; &amp;lt;0x0037....&amp;gt;.&lt;br /&gt;
Since both of these commands are stubbed in the Old3DS NFC module from the very first version(those just return an error), these issues only affect the New3DS NFC module.&lt;br /&gt;
&lt;br /&gt;
There&#039;s no known retail titles which have access to either of these services.&lt;br /&gt;
| ROP under NFC module.&lt;br /&gt;
| New3DS: None&lt;br /&gt;
| New3DS: [[9.5.0-22]]&lt;br /&gt;
| December 2014?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[News_Services|NEWSS]] service command notificationID validation failure&lt;br /&gt;
| This module does not validate the input notificationID for &amp;lt;nowiki&amp;gt;&amp;quot;news:s&amp;quot;&amp;lt;/nowiki&amp;gt; service commands. This is an out-of-bounds array index bug. For example, [[NEWSS:SetNotificationHeader]] could be used to exploit news module: this copies the input data(size is properly checked) to: out = newsdb_savedata+0x10 + (someu32array[notificationID]*0x70).&lt;br /&gt;
| ROP under news module.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.7.0-25|9.7.0-X]]&lt;br /&gt;
| December 2014&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWMUDS:DecryptBeaconData]] heap buffer overflow&lt;br /&gt;
| input_size = 0x1E * &amp;lt;value the u8 from input_[[NWM_Services|networkstruct]]+0x1D&amp;gt;. Then input_tag0 is copied to a heap buffer. When input_size is larger than 0xFA-bytes, it will then copy input_tag1 to &amp;lt;end_address_of_previous_outbuf&amp;gt;, with size=input_size-0xFA.&lt;br /&gt;
&lt;br /&gt;
This can be triggered by either using this command directly, or by boadcasting a wifi beacon which triggers it while a 3DS system running the target process is in range, when the process is scanning for hosts to connect to. Processes will only pass tag data to this command when the wlancommID and other thing(s) match the values for the process.&lt;br /&gt;
&lt;br /&gt;
There&#039;s no known way to actually exploit this for getting ROP under NWM-module, at the time of originally adding this to the wiki. This is because the data which gets copied out-of-bounds *and* actually causes crash(es), can&#039;t be controlled it seems(with just broadcasting a beacon at least). It&#039;s unknown whether this could be exploited from just using NWMUDS service-cmd(s) directly.&lt;br /&gt;
| Without any actual way to exploit this: NWM-module DoS, resulting in process termination(process crash). This breaks *everything* involving wifi comms, a reboot is required to recover from this.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.0.0-20]]&lt;br /&gt;
| ~September 23, 2014(see the [[NWMUDS:DecryptBeaconData]] page history)&lt;br /&gt;
| August 3, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[HID_Services|HID]] module shared-mem&lt;br /&gt;
| HID module does not validate the index values in [[HID_Shared_Memory|sharedmem]](just changes index to 0 when index == maxval when updating), therefore large values will result in HID module writing HID data to arbitrary addresses.&lt;br /&gt;
| ROP under HID module, but this is *very* unlikely to be exploitable since the data written is HID data.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.3.0-21]]&lt;br /&gt;
| 2014?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| gspwn&lt;br /&gt;
| GSP module does not validate addresses given to the GPU. This allows a user-mode application/applet to read/write to a large part of physical FCRAM using GPU DMA. From this, you can overwrite the .text segment of the application you&#039;re running under, and gain real code-execution from a ROP-chain. Normally applets&#039; .text([[Home Menu]], [[Internet Browser]], etc) is located beyond the area accessible by the GPU, except for [[RO_Services|CROs]] used by applets([[Internet Browser]] for example).&lt;br /&gt;
&lt;br /&gt;
FCRAM is gpu-accessible up to physaddr 0x26800000 on Old3DS, and 0x2D800000 on New3DS. This is BASE_memregion_start(aka SYSTEM_memregion_end)-0x400000 (0x800000 with New3DS) with the default memory-layout on Old3DS/New3DS. With [[11.3.0-36|11.3.0-X]] the cutoff now varies due to the new [[SVC]] 0x59. The New3DS &amp;quot;normal&amp;quot;(non-APPLICATION) cutoff was changed to 0x2D000000 due to the new [[SVC]] 0x59.&lt;br /&gt;
| User-mode code execution.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| Early 2014&lt;br /&gt;
| &lt;br /&gt;
| smea, [[User:Yellows8|Yellows8]]/others before then&lt;br /&gt;
|-&lt;br /&gt;
| [[GSP_Services|GSP]] client management failures&lt;br /&gt;
| Shared memory of GSP clients is all on the same page, this allows any GSP client to craft custom GX commands for other clients. Additionally, [[GSPGPU:TriggerCmdReqQueue]] does not check if the calling client has rendering rights.&lt;br /&gt;
&lt;br /&gt;
These two flaws can be used to craft DMA/Transfer Engine commands within a different GSP client to issue reads/writes to both physical (akin to gspwn) and virtual memory of said client.&lt;br /&gt;
| Arbitrary RW from and into a client process.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.17.0-50|11.17.0-50]]&lt;br /&gt;
|&lt;br /&gt;
| May 2025&lt;br /&gt;
| [[User:kynex7510|kynex7510]], probably others&lt;br /&gt;
|-&lt;br /&gt;
| rohax&lt;br /&gt;
| Using gspwn, it is possible to overwrite a loaded [[CRO0]]/[[CRR0]] after its RSA-signature has been validated. Badly validated [[CRO0]] header leads to arbitrary read/write of memory in the ro-process. This gives code-execution in the ro module, who has access to [[SVC|syscalls]] 0x70-0x72, 0x7D.&lt;br /&gt;
&lt;br /&gt;
This was fixed after [[ninjhax]] release by adding checks on [[CRO0]]-based pointers before writing to them.&lt;br /&gt;
| Memory-mapping syscalls.&lt;br /&gt;
| [[9.3.0-21]]&lt;br /&gt;
| [[9.4.0-21]]&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
| smea, [[User:Plutooo|plutoo]] joint effort&lt;br /&gt;
|-&lt;br /&gt;
| Region free&lt;br /&gt;
| Only [[Home Menu]] itself checks gamecards&#039; region when launching them. Therefore, any application launch that is done directly with [[NS]] without signaling Home Menu to launch the app, will result in region checks being bypassed.&lt;br /&gt;
This essentially means launching the gamecard with the [[NS_and_APT_Services|&amp;quot;ns:s&amp;quot;]] service. The main way to exploit this is to trigger a FIRM launch with an application specified, either with a normal FIRM launch or a hardware [[NSS:RebootSystem|reboot]].&lt;br /&gt;
| Launching gamecards from any region + bypassing Home Menu gamecard-sysupdate installation&lt;br /&gt;
| None&lt;br /&gt;
| Last tested with [[10.1.0-27|10.1.0-X]].&lt;br /&gt;
| June(?) 2014&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWM_Services|NWM]] service-cmd state null-ptr deref&lt;br /&gt;
| The NWMUDS service command code loads a ptr from .data, adds an offset to that, then passes that as the state address for the actual command-handler function. The value of the ptr loaded from .data is not checked, therefore this will cause crashes due to that being 0x0 when NWMUDS was not properly initialized.&lt;br /&gt;
It&#039;s unknown whether any NWM services besides NWMUDS have this issue.&lt;br /&gt;
| This is rather useless since it&#039;s only a crash caused by a state ptr based at 0x0.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.0.0-20]]&lt;br /&gt;
| 2013?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== General/CTRSDK ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in version&lt;br /&gt;
!  Last version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] Streetpass message exheader stack-smashing&lt;br /&gt;
| When parsing streetpass messages, &amp;quot;nn::cec::CTR::Message::InputMessage&amp;quot; calls &amp;quot;nn::cec::CTR::Message::SetExHeaderWithoutCalc&amp;quot; for each exheader entry in the input message. The number of entries should not exceed 16 but remains unchecked, leading to a stack-buffer-overflow.&lt;br /&gt;
| ROP under any application parsing Streetpass messages&lt;br /&gt;
Remote code execution under [[CECD_Services|CECD]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| &lt;br /&gt;
| 2019&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWM_Services|UDS]] beacon additional-data buffer overflow&lt;br /&gt;
| Originally CTRSDK did not validate the UDS additional-data size before using that size to copy the additional-data to a [[NWM_Services|networkstruct]]. This was eventually fixed.&lt;br /&gt;
This was discovered while doing code RE with an old dlp-module version. It&#039;s unknown in what specific CTRSDK version this was fixed, or even what system-version updated titles with a fixed version.&lt;br /&gt;
&lt;br /&gt;
It&#039;s unknown if there&#039;s any titles using a vulnerable CTRSDK version which are also exploitable with this(dlp module can&#039;t be exploited with this).&lt;br /&gt;
&lt;br /&gt;
The maximum number of bytes that can be written beyond the end of the outbuf is 0x37-bytes, with additionaldata_size=0xFF.&lt;br /&gt;
| Perhaps ROP, very difficult if possible with anything at all&lt;br /&gt;
| ?&lt;br /&gt;
| &lt;br /&gt;
| September(?) 2014&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| CTPK buffer overflow&lt;br /&gt;
| At offset 0x20 in CTPK is an array for each texture, each entry is 0x20-bytes. This contains a wordindex(entry+0x18) for some srcdata relative to CTPK+0, and an u8 wordsize(entry+0x14) for this data. The CTRSDK function handling this doesn&#039;t validate the size, when copying srcdata using this size to the output buffer. Applications usually have the output buffer on the stack, hence stack buffer overflow.&lt;br /&gt;
&lt;br /&gt;
While CTPK(*.ctpk) are normally only loaded from RomFS, some application(s) load from elsewhere too.&lt;br /&gt;
| ROP under the target application.&lt;br /&gt;
| None?&lt;br /&gt;
| &amp;quot;[SDK+NINTENDO:CTR_SDK-11_4_0_200_none]&amp;quot;&lt;br /&gt;
| November 14, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Pia vulns&lt;br /&gt;
| [https://switchbrew.org/wiki/Switch_System_Flaws#Pia Originally discovered in Pia v5.x for Switch], these vulns are also present in earlier versions (v3.x/4.x/5.x, possibly earlier?) for 3DS (and Wii U too).&lt;br /&gt;
Pia encryption generally wasn&#039;t used pre-Switch (sent packets are plaintext). 3DS is affected by all Pia vulns listed above except for LAN. The functionality for ParseLeaveMeshInvitation doesn&#039;t exist in 3DS Pia v3.9.2. Wii U is affected by all listed Pia vulns except for the LAN vulns.&lt;br /&gt;
| See [https://switchbrew.org/wiki/Switch_System_Flaws#Pia here].&lt;br /&gt;
| Unfixed on 3DS/Wii U&lt;br /&gt;
| &amp;quot;[SDK+Nintendo:PIA_5_4_3]&amp;quot;&lt;br /&gt;
| See [https://switchbrew.org/wiki/Switch_System_Flaws#Pia here]; separately checked later (UpdateConnectionReport) by [[User:Riley|Riley]] on: June 14, 2023&lt;br /&gt;
| [[User:Yellows8|Yellows8]]; added to 3dbrew (UpdateConnectionReport) by [[User:Riley|Riley]] later&lt;br /&gt;
|-&lt;br /&gt;
| pialease nerf: stack overflow in Pia when parsing UDS packet cmd=5 &amp;quot;UpdateMigrationNodeInfoMessage&amp;quot;&lt;br /&gt;
| A UDS packet as received by Pia contains a command type, where cmd=1 is higher-layer game-data, and other cmds are parsed internally.&lt;br /&gt;
&lt;br /&gt;
A function named &amp;quot;UdsNode::ParseUpdateMigrationNodeInfoMessage&amp;quot; is called to handle packets with cmd=5.&lt;br /&gt;
&lt;br /&gt;
This checks the player nodeID (returns if not player 1, that is, UDS network host), then calls an additional function which does a loop of 64-bit copies to a fixed-size stack buffer using unchecked index and data from the received packet contents.&lt;br /&gt;
&lt;br /&gt;
This therefore leads to trivial RCE (of every UDS network client) by just sending a single UDS packet; only 0xC u64s on stack can be overwritten easily, but just 2 is enough to start a ROP chain and pivot to the rest of the UDS packet contents elsewhere on the stack.&lt;br /&gt;
&lt;br /&gt;
To exploit some games, an attacker would need to also reimplement the DLP server protocol (and any quirks that game has when parsing the UDS network passphrase obtained from the DLP server). One game that requires this is Mario Party: Island Tour (only the dlplay child connects to a UDS network).&lt;br /&gt;
&lt;br /&gt;
Earliest version of Pia known to be vulnerable is v2.x. v1.x still parses this packet, but does not have the stack-copy loop (index is still unchecked there leading to heap overflow but due to overwrites not being contiguous in memory it may or may not be exploitable).&lt;br /&gt;
&lt;br /&gt;
Fixed with Pia version 4.x, which refactored the UDS send/receive wrapper code and parses completely different commands.&lt;br /&gt;
| ROP under the vulnerable application. A server can exploit every client connected to it; a client can exploit every other client connected to that server.&lt;br /&gt;
| &amp;quot;[SDK+Nintendo:PIA_4_2_0]&amp;quot;&lt;br /&gt;
| &amp;quot;[SDK+Nintendo:PIA_3_10_2]&amp;quot;, &amp;quot;[SDK+Nintendo:PIA_4_2_0]&amp;quot;&lt;br /&gt;
| Discovery: June 3, 2023.&lt;br /&gt;
&lt;br /&gt;
Wiki: November 20, 2023.&lt;br /&gt;
| [[User:Riley|Riley]]&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=3DS_System_Flaws&amp;diff=23439</id>
		<title>3DS System Flaws</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=3DS_System_Flaws&amp;diff=23439"/>
		<updated>2025-05-01T18:29:12Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: GSP behaviour&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Exploits are used to execute unofficial code (homebrew) on the Nintendo 3DS. This page is a list of publicly known system flaws, for userland applications/applets flaws see [[3DS_Userland_Flaws|here]].&lt;br /&gt;
&lt;br /&gt;
=Stale / Rejected Efforts=&lt;br /&gt;
* In the early days of 3DS hacking, Neimod was working on a RAM dumping setup for a while. He has de-soldered the 3DS&#039;s RAM chip and hooked it and the RAM pinouts on the 3DS&#039;s PCB up to a custom RAM dumping setup. He &#039;&#039;has&#039;&#039; published photos showing his setup to be working quite well, with the 3DS successfully booting up, but however, his flickr stream is now private along with most of his work and this method has been unreleased. RAM dumping can be done through homebrew now, making this method obsolete regardless.&lt;br /&gt;
&lt;br /&gt;
==Tips and info==&lt;br /&gt;
The 3DS uses the XN feature of the ARM11 processor. There&#039;s no official way from applications to enable executable permission for memory containing arbitrary unsigned code(there&#039;s a [[SVC]] for this, but only [[RO_Services|RO-module]] has access to it). A usable userland exploit would still be useful: you could only do return-oriented-programming with it initially. From ROP one could then exploit system flaw(s), see below.&lt;br /&gt;
&lt;br /&gt;
SD card [[extdata]] and SD savegames can be attacked, for consoles where the console-unique [[Nand/private/movable.sed|movable.sed]] was dumped(accessing SD data is far easier by running code on the target 3DS however).&lt;br /&gt;
&lt;br /&gt;
=System flaws=&lt;br /&gt;
== Hardware ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Fixed with hardware model/revision&lt;br /&gt;
!  Newest hardware model/revision this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| ARM9/ARM11 bootrom vectors point at uninitialized RAM&lt;br /&gt;
| ARM9&#039;s and ARM11&#039;s exception vectors are hardcoded to point at the CPU&#039;s internal memory (0x08000000 region for ARM9, AXIWRAM for ARM11). While the bootrom does set them up to point to an endless loop at some point during boot, it does not do so immediately. As such, a carefully-timed fault injection (via hardware) to trigger an exception (such as an invalid instruction) will cause execution to fall into ARM9 RAM. &lt;br /&gt;
Since RAM isn&#039;t cleared on boot (see below), one can immediately start execution of their own code here to dump bootrom, OTP, etc.&lt;br /&gt;
The ARM9 bootrom does the following at reset:  reset vector branches to another instruction, then branches to bootrom+0x8000. Hence, there&#039;s no way to know for certain when exactly the ARM9 exception-vector data stored in memory gets initialized.&lt;br /&gt;
&lt;br /&gt;
The vulnerable timing range is about 100 CPU cycles after they start (which happens after the PLLs have stabilized after power-up). A glitch needs to be injected during one of these 100 cycles for the attack to succeed.&lt;br /&gt;
&lt;br /&gt;
It has been exploited by derrek to dump the ARM9 bootrom as of Summer 2015.&lt;br /&gt;
| None: all available 3DS models at the time of writing have the exact same ARM9/ARM11 bootrom for the unprotected areas.&lt;br /&gt;
| New3DS&lt;br /&gt;
| End of February 2014&lt;br /&gt;
| [[User:Derrek|derrek]], WulfyStylez (May 2015) independently&lt;br /&gt;
|-&lt;br /&gt;
| Missing AES key clearing&lt;br /&gt;
| The hardware AES engine does not clear keys when doing a hard reset/reboot.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| August 2014&lt;br /&gt;
| Mathieulh/Others&lt;br /&gt;
|-&lt;br /&gt;
| No RAM clearing on reboots&lt;br /&gt;
| On an MCU-triggered reboot all RAM including FCRAM/ARM9 memory/AXIWRAM/VRAM keeps its contents.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| March 2014&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| 32bits of actual console-unique TWLNAND keydata&lt;br /&gt;
| On retail the 8-bytes at ARM9 address [[Memory_layout|0x01FFB808]] are XORed with hard-coded data, to generate the TWL console-unique keys, including TWLNAND. On Old3DS the high u32 is always 0x0, while on New3DS that u32 is always 0x2. On top of this, the lower u32&#039;s highest bit is always ORed. only 31 bits of the TWL console-unique keydata / TWL consoleID are actually console-unique.&lt;br /&gt;
This allows one to easily bruteforce the TWL console-unique keydata with *just* data from TWLNAND. On DSi the actual console-unique data for key generation is 8-bytes(all bytes actually set).&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| 2012?&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| DSi / 3DS-TWL key-generator&lt;br /&gt;
| After using the key generator to generate the normal-key, you could overwrite parts of the normal-key with your own data and then recover the key-generator output by comparing the new crypto output with the original crypto output. From the normal-key outputs, you could deduce the TWL key-generator function.&lt;br /&gt;
This applies to the keyX/keyY too.&lt;br /&gt;
&lt;br /&gt;
This attack does not work for the 3DS key-generator because keyslots 0-3 are only for TWL keys.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| 2011&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| 3DS key-generator&lt;br /&gt;
| The algorithm for generating the normal-keys for keyslots is cryptographically weak.  As a result, it is easily susceptible to differential cryptanalysis if the normal-key corresponding to any scrambler-generated keyslot is discovered.&lt;br /&gt;
&lt;br /&gt;
Several such pairs of matching normal-keys and KeyY values were found, leading to deducing the key-generator function.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| February 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]], [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| RSA keyslots don&#039;t clear exponent when setting modulus&lt;br /&gt;
| The [[RSA_Registers|RSA keyslots]] are set by boot ROM to have four private RSA keys.  The exponent value in the RSA registers is write-only and not readable.&lt;br /&gt;
&lt;br /&gt;
However, when setting a keyslot&#039;s modulus, the RSA hardware leaves the exponent alone.  This allows retrieving the exponent by doing a discrete logarithm of the output.&lt;br /&gt;
&lt;br /&gt;
By setting the modulus to a prime number whose modular multiplicative order is &amp;quot;smooth&amp;quot; (that is, p-1 is divisible by only small prime numbers), discrete logarithms can be calculated quickly using the [[wikipedia:Pohlig-Hellman algorithm|Pohlig-Hellman algorithm]].  If the prime chosen is greater than the modulus, but the same bit size, the discrete logarithm is the private exponent.&lt;br /&gt;
&lt;br /&gt;
This exploit&#039;s usefulness is limited: RSA keyslot 0 is only used in current firmware for deriving the 6.x save and 7.x NCCH keys, which were already known, and the other three keyslots are entirely unused.  Additionally, with a boot ROM dump, this exploit is moot; these private keys are located in the protected ARM9 boot ROM.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| March 2016&lt;br /&gt;
| [[User:Myria|Myria]]&lt;br /&gt;
|-&lt;br /&gt;
| [[CONFIG11_Registers#CFG11_GPUPROT|CFG11_GPUPROT]] allowing acccess to AXIWRAM/FCRAM-BASE-memregion&lt;br /&gt;
| [[CONFIG11_Registers#CFG11_GPUPROT|CFG11_GPUPROT]] can be configured by anything with access to it to allow the GPU to access the entire AXIWRAM+FCRAM. For example, this is an issue for any sysmodule that gets exploited and has access to this register memory-page(include one that&#039;s listed below).&lt;br /&gt;
&lt;br /&gt;
See also &amp;quot;kernelhax via gspwn&amp;quot; below.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| February 7, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Boot ROM ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Fixed with hardware model/revision&lt;br /&gt;
!  Newest hardware model/revision this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| FIRM partitions known-plaintext&lt;br /&gt;
| The [[Flash_Filesystem|FIRM partitions]] are encrypted with AES-CTR without a MAC. Since this works by XOR&#039;ing data with a static (per-console in this case) keystream, one can deduce the keystream of a portion of each FIRM partition if they have the actual FIRM binary stored in it.&lt;br /&gt;
&lt;br /&gt;
This can be paired with many exploits. For example, it allows minor FIRM downgrades (i.e. 10.4 to 9.6 or 9.5 to 9.4, but not 9.6 to 9.5).&lt;br /&gt;
However it is most commonly used to install arbitrary FIRMs (usually boot9strap), thanks to sighax.&lt;br /&gt;
&lt;br /&gt;
This can be somewhat addressed by having a FIRM header skip over previously used section offsets, but this would just air-gap newer FIRMs without fixing the core bug. This can also only be done a limited number of times due to the size of FIRM versus the size of the partitions.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| &lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| Boot9 AES keyinit function issues&lt;br /&gt;
| [[Bootloader|Boot9]] seems to have two bugs in the AES key-init function, see [[AES_Registers#AES_key-init|here]].&lt;br /&gt;
| None&lt;br /&gt;
| BootROM issue.&lt;br /&gt;
| 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| New3DS has same boot ROM as Old3DS&lt;br /&gt;
| The New3DS has the exact same boot ROM as the Old3DS.  This means, among other things, that all the same boot ROM flaws are present.  Also, this meant that it is possible to boot Old3DS firmware on New3DS (see &amp;quot;CFG9_SYSPROT9 bit1 not set by Kernel9&amp;quot;).&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| October 2014&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| sighax: Boot9 improper validation of FIRM partition RSA signatures&lt;br /&gt;
| The [[Flash_Filesystem|FIRM partitions]] are signed with RSA-2048 using SHA-256 and PKCS #1 v1.5 padding.  Boot9, however, improperly validates the padding in three ways:&lt;br /&gt;
# Boot9 permits block type 02, meant for encrypted messages, to be used for signatures.  Only 01, for signatures, should have been permitted.  As a result, when using block type 02, a signature block is not required to have a long string of FF bytes as padding, but rather any nonzero random values suffice.&lt;br /&gt;
# Boot9 does not require that the length of the padding fill out the signature block completely.  As a result, there is considerable freedom in the layout of a signature.&lt;br /&gt;
# Boot9 fails to do bounds checking in its parsing of the DER-encoded hash algorithm type and hash value; the length values given in DER are permitted to point outside the signature block.&lt;br /&gt;
Flaw 3 allows the DER encoding to be such that boot9 believes that the signature&#039;s hash value is outside the range of the block itself, somewhere on the stack.  This can be pointed at the correct hash value it computes.  Boot9 then memcmp&#039;s the calculated hash against itself, and thinks that the hash is valid.&lt;br /&gt;
&lt;br /&gt;
As a result of the above, we estimate that one in 2&amp;lt;sup&amp;gt;43&amp;lt;/sup&amp;gt; (~8.8 trillion) random fake signatures will be considered by Boot9 to be valid.  This is well within the range of brute force, particularly with an optimized GPU implementation.  An Nvidia GTX 1080 Ti would take about one week to find a match.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| July 2015&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| Boot9 FIRM loading doesn&#039;t blacklist memory-mapped I/O&lt;br /&gt;
| [[Bootloader|Boot9]]&#039;s FIRM loading blacklists Boot9 data regions, but forgets to do other important regions, including Memory-mapped I/O. Combined with sighax, a malicious FIRM can be used to overwrite:&lt;br /&gt;
a) boot9 data-abort handler, coupled with a 4th section that tries to NDMA copy to NULL, causing a data abort&lt;br /&gt;
&lt;br /&gt;
b) boot9 IRQ handler (this has the disadvantage that you must restore the original handler, then call it manually when your payload runs)&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| 2015(?)&lt;br /&gt;
| [[User:Derrek|derrek]] (2015?), [[User:Normmatt|Normmatt]] and [[User:SciresM|SciresM]] independently (January 2017).&lt;br /&gt;
|-&lt;br /&gt;
| &amp;quot;superhax&amp;quot;: Boot9 FIRM loading blacklist check is flawed&lt;br /&gt;
| Boot9 only makes sure the &#039;&#039;&#039;start&#039;&#039;&#039; and &#039;&#039;&#039;end&#039;&#039;&#039; address of each section is not covered by a blacklisted region. Thus, it is possible to overwrite blacklisted regions (e.g. ARM9 Exception Vectors) by choosing a FIRM section range that encloses an entire blacklisted region. The vulnerable code looks like this: if(blRegions[i].start &amp;lt;= sectionStart &amp;amp;&amp;amp; blRegions[i].end &amp;gt; sectionStart &amp;lt;nowiki&amp;gt;||&amp;lt;/nowiki&amp;gt; blRegions[i].start &amp;lt;= sectionEnd &amp;amp;&amp;amp; blRegions[i].end &amp;gt; sectionEnd) return false; // failure&lt;br /&gt;
The boot9 vector table (0x08000000) contains 6 entries, each 8-bytes wide (0x30 bytes); Only 0x08000000 through 0x08000040 are blacklisted, and boot9 doesn&#039;t use the region after the vector table (this is convenient because we can put any payload we want after it and not worry about overwriting chunks of boot9 code)&lt;br /&gt;
&lt;br /&gt;
To exploit this, craft a FIRM section payload that&#039;s loaded a few bytes before 0x08000000, add padding to get to 0x08000000 and overwrite the vector table; You could overwrite the data-abort vector and craft a 4th FIRM section that causes a data-abort OR you can just overwrite the IRQ function pointer at 0x08000004 (make sure your payload replaces the original boot9 function pointer); you can point the rest of the vectors to infinite loops since they shouldn&#039;t be triggered.&lt;br /&gt;
| None&lt;br /&gt;
| New3DS&lt;br /&gt;
| August 2015&lt;br /&gt;
| [[User:Plutoo|plutoo]], [[User:Yellows8|yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ARM9 software ==&lt;br /&gt;
&lt;br /&gt;
=== arm9loader ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Public disclosure timeframe&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| Generating the keysector console-unique keys with ITCM+Boot9&lt;br /&gt;
| [[Bootloader|Boot9]] decrypts the 0x100-byte [[OTP_Registers|OTP]] using AES-CBC with keydata stored in Boot9. If hash verification is successful, the plaintext of the first 0x90-bytes are copied into [[Memory_layout|ITCM]]. This is the &#039;&#039;exact&#039;&#039; &#039;&#039;same&#039;&#039; region hashed by arm9loader when generating the console-unique keys for decrypting the keysector, except arm9loader uses the raw encrypted OTP.&lt;br /&gt;
&lt;br /&gt;
Therefore, with the OTP keydata+IV from Boot9 you can: encrypt the 0x90-bytes from ITCM, then hash the output to get the console-unique keys for the system&#039;s keysector. This can even be done for Old3DS which doesn&#039;t have the arm9loader keysector officially.&lt;br /&gt;
&lt;br /&gt;
It&#039;s unknown why arm9loader only used the first 0x90-bytes of OTP. Using more data from OTP would&#039;ve prevented this. Fixing this would require doing exactly that, but that would also mean updating the NAND keysector(which is dangerous).&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| &lt;br /&gt;
| 2015&lt;br /&gt;
| January 6, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Rearrangable keys in the NAND keystore&lt;br /&gt;
| Due to the keystore being encrypted with AES-ECB, one can rearrange blocks and still have the NAND keystore decrypt in a deterministic way. &lt;br /&gt;
&lt;br /&gt;
Using 10.0 FIRM it is possible to rearrange keys such that ARM9 memory is executed. As such using existing ARM9 execution 10.0 FIRM can be written to NAND and a payload written to memory, with the payload to be executed post-K9L using an MCU reboot.&lt;br /&gt;
| arm9loaderhax given existing ARM9 code execution&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| Early 2016&lt;br /&gt;
| 27 September 2016&lt;br /&gt;
| Myria, [[User:Dark samus|dark_samus]]; mathieulh (independently); [[User:Plutooo|plutoo]] (independently) + others&lt;br /&gt;
|-&lt;br /&gt;
| Uncleared OTP hash keydata in console-unique 0x11 key-generation&lt;br /&gt;
| Kernel9Loader does not clear the [[SHA_Registers#SHA_HASH|SHA_HASH register]] after use. As a result, the data stored here as K9L hands over to Kernel9 is the hash of [[OTP_Registers|OTP data]] used to seed the [[FIRM#New_3DS_FIRM|console-unique NAND keystore decryption key]] set on keyslot 0x11.&lt;br /&gt;
&lt;br /&gt;
Retrieving this keydata and the [[Flash_Filesystem#0x12C00|NAND keystore]] of the same device allows calculating the decrypted New3DS NAND keystore (non-unique, common to all New3DS units), which contains AES normal keys, also set on keyslot 0x11, which are then used to derive all current [[AES_Registers#Keyslots|New3DS-only AES keyXs]] including the newer batch introduced in [[9.6.0-24#arm9loader|9.6.0-X]]. From there, it is trivial to perform the same key derivation in order to initialize those keys on any system version, and even on Old3DS.&lt;br /&gt;
&lt;br /&gt;
This can be performed by exploiting the &amp;quot;arm9loaderhax&amp;quot; vulnerability to obtain post-K9L code execution after an MCU reboot (the bootrom section-loading fail is not relevant here, this attack was performed without OTP data by brute-forcing keys), and using this to dump the SHA_HASH register. This attack works on any FIRM version shipping a vulnerable version of K9L, whereas OTP dumping required a boot of &amp;lt;[[3.0.0-6|3.0.0-X]].&lt;br /&gt;
&lt;br /&gt;
This attack results in obtaining the entire (0x200-bytes) NAND keystore - it was confirmed at a later date that this keystore is encrypted with the same key (by comparing the decrypted data from multiple units), and therefore using another key in this store will not remedy the issue as all keys are known (i.e. later, unused keys decrypt to the same 0x200-bytes constant with the same OTP hash). Later keys could have been encrypted differently but this is not the case. As a result of this, it is not possible for Nintendo to use K9L again in its current format for its intended purpose, though this was not news from the moment people dumped a New3DS OTP.&lt;br /&gt;
| Derivation of all New3DS keys generated via the NAND keystore (0x1B &amp;quot;Secure4&amp;quot; etc.)&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| ~April 2015, implemented in May 2015&lt;br /&gt;
| 13 January 2016&lt;br /&gt;
| [[User:WulfyStylez|WulfyStylez]], [[User:Dazzozo|Dazzozo]], [[User:Shinyquagsire23|shinyquagsire23]] (complimentary + implemented), [[User:Plutooo|plutoo]], Normmatt (discovered independently)&lt;br /&gt;
|-&lt;br /&gt;
| enhanced-arm9loaderhax&lt;br /&gt;
| See the 32c3 3ds talk.&lt;br /&gt;
Since this is a combination of a trick with the arm9-bootrom + arm9loaderhax, and since you have to manually write FIRM to the firm0/firm1 NAND partitions, this can&#039;t be completely fixed. Any system with existing ARM9 code execution and an OTP/OTP hash dump can exploit this. Additionally, by using the FIRM partition known-plaintext bug and bruteforcing the second entry in the keystore, this can currently be exploited on all New3DS systems without any other prerequisite hacks.&lt;br /&gt;
| arm9loaderhax which automatically occurs at hard-boot.&lt;br /&gt;
| See arm9loaderhax / description.&lt;br /&gt;
| See arm9loaderhax / description.&lt;br /&gt;
| Theorized around mid July, 2015. Later implemented+tested by [[User:Plutooo|plutoo]] and [[User:Derrek|derrek]].&lt;br /&gt;
| 32c3 3ds talk (December 27, 2015)&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| arm9loaderhax: Missing verification block for the 9.6 keys&lt;br /&gt;
| Starting with [[9.6.0-24|9.6.0-X]] a new set of NAND-based keys were introduced. However, no verification block was added to verify that the new key read from NAND is correct. This was technically an issue from [[9.5.0-22|9.5.0-X]] with the original sector+0 keydata, however the below is only possible with [[9.6.0-24|9.6.0-X]] since keyslots 0x15 and 0x16 are generated from different 0x11 keyXs.&lt;br /&gt;
&lt;br /&gt;
Writing an incorrect key to NAND will cause arm9loader to decrypt the ARM9 kernel as garbage and then jump to it.&lt;br /&gt;
&lt;br /&gt;
This allows an hardware-based attack where you can boot into an older exploited firmware, fill all memory with NOP sleds/jump-instructions, and then reboot into executing garbage. By automating this process with various input keydata, eventually you&#039;ll find some garbage that jumps to your code.&lt;br /&gt;
&lt;br /&gt;
This gives very early ARM9 code execution (pre-ARM9 kernel). As such, it is possible to dump RSA keyslots with this and calculate the 6.x [[Savegames#6.0.0-11_Savegame_keyY|save]], and 7.x [[NCCH]] keys. This cannot be used to recover keys initialized by arm9loader itself. This is due to it wiping the area used for its stack during NAND sector decryption and keyslot init. &lt;br /&gt;
&lt;br /&gt;
Due to FIRMs on both Old and New 3DS using the same RSA data, this can be exploited on Old3DS as well, but only if one already has the actual plaintext normalkey from New3DS NAND sector 0x96 offset-0 and has dumped the OTP area of the Old3DS.&lt;br /&gt;
| Recovery of 6.x [[Savegames#6.0.0-11_Savegame_keyY|save key]]/7.x [[NCCH]] key, access to uncleared OTP hash keydata&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| arm9loader runs on Old3DS&lt;br /&gt;
| Despite being written only for New3DS, all of arm9loader runs fine on Old3DS.  It&#039;s not until booting Kernel9 that a New3DS FIRM partition would crash on an Old3DS.  As a result, if a bug exists in arm9loader to get control, it can be exploited on Old3DS by writing New3DS FIRM to the FIRM partitions.  Thus, arm9loaderhax works on both Old3DS and New3DS.&lt;br /&gt;
| arm9loader bugs also compromise Old3DS&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| Sometime in 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]] presumably&lt;br /&gt;
|-&lt;br /&gt;
| Uncleared New3DS keyslot 0x11&lt;br /&gt;
| Originally the New3DS [[FIRM]] arm9bin loader only cleared keyslot 0x11 when it gets executed at firmlaunch. This was fixed with [[9.5.0-22|9.5.0-X]] by completely clearing keyslot 0x11 immediately after the loader finishes using keyslot 0x11.&lt;br /&gt;
This means that any ARM9 code that can execute before the loader clears the keyslot at firmlaunch(including firmlaunch-hax) can get access to the uncleared keyslot 0x11, which then allows one to generate all &amp;lt;=v9.5 New3DS keyXs which are generated by keyslot 0x11.&lt;br /&gt;
&lt;br /&gt;
Therefore, to completely fix this the loader would have to generate more keys using different keyslot 0x11 keydata. This was done with [[9.6.0-24|9.6.0-X]].&lt;br /&gt;
| New3DS keyXs generation&lt;br /&gt;
| Mostly fixed with [[9.5.0-22|9.5.0-X]], completely fixed with new keys with [[9.6.0-24|9.6.0-X]].&lt;br /&gt;
| &lt;br /&gt;
| February 3, 2015 (one day after [[9.5.0-22|9.5.0-X]] release)&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Process9 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Public disclosure timeframe&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| Leak of normal-key matching a key-scrambler key&lt;br /&gt;
| New 3DS firmware versions [[8.1.0-0 New3DS|8.1.0]] through [[9.2.0-20|9.2.0]] set the encryption key for [[Amiibo]] data using a hardcoded normal-key in Process9.  In firmware [[9.3.0-21|9.3.0]], Nintendo &amp;quot;fixed&amp;quot; this by using the key scrambler instead, by calculating the keyY value for keyslot 0x39 that results in the same normal-key, then hardcoding that keyY into Process9.&lt;br /&gt;
&lt;br /&gt;
Nintendo&#039;s fix is actually the problem: Nintendo revealed the normal-key matching an unknown keyX and a known keyY.  Combined with the key scrambler using an insecure scrambling algorithm (see &amp;quot;Hardware&amp;quot; above), the key scrambler function could be deduced.&lt;br /&gt;
| Deducing the keyX for keyslot 0x39 and the key scrambler algorithm&lt;br /&gt;
| New 3DS [[9.3.0-21|9.3.0-X]], sort of&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| Sometime in 2015 after the hardware key-generator was broken.&lt;br /&gt;
| 32c3 3ds talk (December 27, 2015)&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Leak of normal-key matching a key-generator key&lt;br /&gt;
| During the 3DS&#039; development (June/July 2010) Nintendo added support installing encrypted content ([[CIA]]). Common-key index1 was intended to be a [[AES|hardware generated key]]. However while they added code to generate the key in hardware, they forgot to remove the normal-key for index1 (used elsewhere, likely old debug code). Nintendo later removed the normal key sometime before the first non-prototype firmware release.&lt;br /&gt;
&lt;br /&gt;
Knowing the keyY and the normal-key for common-key index1, the devkit key-generator algorithm can be deduced (see &amp;quot;Hardware&amp;quot; above). Additionally the remaining devkit common-keys can be generated once the common-key keyX is recovered.&lt;br /&gt;
&lt;br /&gt;
Note that the devkit key-generator was discovered to be the same as the retail key-generator.&lt;br /&gt;
| Deducing the keyX for keyslot 0x3D and hardware key-generator algorithm. Generate remaining devkit common-keys.&lt;br /&gt;
| pre-[[1.0.0-0|1.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| Shortly after the key-generator was revealed to be flawed at the 32c3 3ds talk&lt;br /&gt;
| January 20, 2016&lt;br /&gt;
| [[User:Jakcron|jakcron]]&lt;br /&gt;
|-&lt;br /&gt;
| Factory firmware is vulnerable to sighax&lt;br /&gt;
| During the 3DS&#039;s development, presumably boot9 was written (including the sighax vulnerability). This vulnerability is also present in factory firmware (and earlier, including 0.11). This was fixed in version 1.0.0-0.&lt;br /&gt;
| Deducing the mechanics of the sighax vulnerability in boot9 without having a dump of protected boot9. ARM9 code execution on factory/earlier firmware.&lt;br /&gt;
| [[1.0.0-0|1.0.0-X]]&lt;br /&gt;
| [[1.0.0-0|1.0.0-X]]&lt;br /&gt;
| May 9, 2017&lt;br /&gt;
| May 19, 2017&lt;br /&gt;
| [[User:SciresM|SciresM]], [[User:Myria|Myria]]&lt;br /&gt;
|-&lt;br /&gt;
| safecerthax &lt;br /&gt;
| O3DS &amp;amp; O2DS SAFE_FIRM is still vulnerable to the PXIAM:ImportCertificates flaw fixed in [[5.0.0-11]] and to SSLoth fixed in [[11.14.0-46]]. It makes it possible to spoof the official NUS update server and remotely trigger the vulnerability in SAFE_FIRM.&lt;br /&gt;
| Remote Arm9 code execution in O3DS/O2DS SAFE_FIRM&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2020&lt;br /&gt;
| December 18, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| twlhax: Corrupted SRL header leads to memory overwrite&lt;br /&gt;
| During TWL_FIRM boot, the ARM11 process TwlBg puts launcher.srl, the DSi bootloader, into FCRAM.  TWL_FIRM Process9 then parses the [http://dsibrew.org/wiki/NDS_Format SRL header] to place launcher.srl&#039;s code where DSi mode can execute it.&lt;br /&gt;
&lt;br /&gt;
DSi-mode memory is in FCRAM, but interleaved.  Each byte of DSi-mode memory also exists at some address in 3DS FCRAM space.&lt;br /&gt;
&lt;br /&gt;
Process9 does not validate the RSA signature on launcher.srl, unlike SRLs loaded from cartridge or NAND (DSiWare).  A compromised ARM11 can, in a manner similar to firmlaunchhax, send a launcher.srl with a modified SRL header.  By setting the SRL header&#039;s ARM7/ARM9 load addresses and sizes carefully, accounting for the different memory map and for DSi mode&#039;s interleaved memory, it is possible to overwrite part of Process9&#039;s stack and take control with a ROP chain.&lt;br /&gt;
&lt;br /&gt;
Fixed in 11.8.0-X by... (fill me in)&lt;br /&gt;
| ARM9 code execution (whilst still in 3DS mode)&lt;br /&gt;
| [[11.8.0-41|11.8.0-X]]&lt;br /&gt;
| [[11.8.0-41|11.8.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| August 11, 2018&lt;br /&gt;
| smea&lt;br /&gt;
|-&lt;br /&gt;
| agbhax&lt;br /&gt;
| This is the same issue as twlhax above. Legacy FIRMs share the same OS code (Arm9-side OS, Arm11 kernel), and therefore, the outdated AGB_FIRM can be tricked into executing the still vulnerable PrepareArm9ForTwl function.&lt;br /&gt;
| ARM9 code execution (whilst still in 3DS mode)&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| December 17, 2020&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| safefirmhax&lt;br /&gt;
| SAFE_MODE_FIRM is almost never updated(even when NATIVE_FIRM is updated for vuln fixes), this can be noticed by &#039;&#039;just&#039;&#039; checking 3dbrew/ninupdates title-listings.&lt;br /&gt;
&lt;br /&gt;
The fix for firmlaunch-hax was only applied to NATIVE_FIRM in [[9.5.0-22|9.5.0-X]], leaving SAFE_FIRM exploitable. With ARM11-kernel execution, one can trigger FIRM-launch in to SAFE_FIRM, do Kernel9 &amp;lt;=&amp;gt; Kernel11 sync, PXI sync and then repeat the original attack on SAFE_FIRM instead.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| 2012-2013?&lt;br /&gt;
| Wiki: January 2, 2017&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| safefirmhax 1.1&lt;br /&gt;
| Nintendo&#039;s original safefirmhax fix was flawed -- they added a global boolean that got set to true whenever a non-sysmodule title got launched (except for a hardcoded repair title id), and panic()&#039;d if that boolean was true to prevent launching safefirm after hax was active. However, because the boolean was initially false after firmlaunch -- With ARM11-kernel execution, one could FIRM-launch into NATIVE_FIRM, and then immediately FIRM-launch again into SAFE_FIRM early in NATIVE_FIRM boot before the boolean got set to true to repeat the safehax attack.&lt;br /&gt;
&lt;br /&gt;
This was fixed by adding additional CFG9_BOOTENV checks to firmlaunch code in 11.4.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[11.4.0-37|11.4.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| safefirmhax fix&lt;br /&gt;
| Wiki: April 10, 2017&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| ntrcardhax&lt;br /&gt;
| When reading the banner of a NTR title, Process9 relies on a hardware register to know when the banner was fully read.&lt;br /&gt;
However that register is shared between the ARM9 and the ARM11.&lt;br /&gt;
An attacker with k11 control can so make Process9 believe the banner continues forever and so trigger a buffer overflow.&lt;br /&gt;
With a custom banner for a NTR flashcart, this leads to code execution in Process9.&lt;br /&gt;
&lt;br /&gt;
This was fixed by adding bound checks on the read data.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[10.4.0-29|10.4.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| March 2015&lt;br /&gt;
| 32c3 3ds talk (December 27, 2015)&lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| Title downgrading via [[Application_Manager_Services|AM]]([[Application_Manager_Services_PXI|PXI]])&lt;br /&gt;
| When a title is *already* installed, Process9 will compare the installed title-version with the title-version being installed. When the one being installed is older, Process9 would return an error.&lt;br /&gt;
&lt;br /&gt;
However, this can be bypassed by just deleting the title first via the service command(s) for that: with the title removed from the [[Title_Database]], Process9 can&#039;t compare the input title-version with anything. Hence, titles can be downgraded this way.&lt;br /&gt;
&lt;br /&gt;
[[11.0.0-33|11.0.0-X]] fixed this for key system titles (MSET, Home Menu, spider, ErrDisp, SKATER, NATIVE_FIRM, and every retail system module), by checking the version of the title to install against a hard-coded list of (titleID, minimumVersionRequired) pairs.&lt;br /&gt;
| Bypassing title version check at installation, which then allows downgrading any title.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]], for key system titles.&lt;br /&gt;
| NATIVE_FIRM / AM-sysmodule [[11.0.0-33|11.0.0-X]]&lt;br /&gt;
| ?&lt;br /&gt;
| &lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| Anti-downgrade list did not include all system titles initially&lt;br /&gt;
| The anti-downgrade list did not include legacy FIRMs until [[11.8.0-41|11.8.0-X]]. Therefore, legacy FIRMs could still be downgraded.&lt;br /&gt;
| Downgrading legacy FIRMs; allowing to exploit bugs in older legacy FIRMs (of which at least one exists, see below).&lt;br /&gt;
| [[11.8.0-33|11.8.0]]&lt;br /&gt;
| [[11.8.0-33|11.8.0]]&lt;br /&gt;
| ?&lt;br /&gt;
| Wiki: August 5, 2018&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| TWL_FIRM cmd-9 unchecked offset&lt;br /&gt;
| In [[1.0.0-0|1.0.0-X]]&#039;s TWL_FIRM, cmds 8 and 9 were not stubbed (whereas in the corresponding NATIVE_FIRM, they were).&lt;br /&gt;
Command 8 does the Process9 initialisation for NTR carts if an NTR cart is inserted (NTR, not TWL, judged by chipid).&lt;br /&gt;
&lt;br /&gt;
Command 9 takes (u32 offset_read, u32 offset_write, u32 offset_read_end), and basically just copies (offset_read_end - offset_read) bytes starting at (offset_read) of [NTR cart header in arm9mem, NTR secure area in fcram, TWL secure area in fcram], to 0x18001000 + offset_write + offset_read.&lt;br /&gt;
&lt;br /&gt;
offset_write is not checked at all, thus this leads to ARM9 code execution as long as any NTR cart, including flashcarts that would normally be blocked by TWL_FIRM, is inserted.&lt;br /&gt;
&lt;br /&gt;
In [[2.0.0-2|2.0.0-X]] TWL_FIRM, those commands were stubbed out.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[2.0.0-2|2.0.0-X]]&lt;br /&gt;
| [[2.0.0-2|2.0.0-X]]&lt;br /&gt;
| January 2018&lt;br /&gt;
| Wiki: August 5, 2018&lt;br /&gt;
| [[User:Riley|Riley]]&lt;br /&gt;
|-&lt;br /&gt;
| FIRM launch doesn&#039;t check target FIRM version&lt;br /&gt;
| When executing a FIRM launch, Process9 doesn&#039;t validate that the target FIRM isn&#039;t an old version.  This allows booting an exploitable FIRM from a newer FIRM, if you can get the exploitable FIRM installed.  ([[11.0.0-33|11.0.0-X]] now prevents installing old versions of system titles, but this doesn&#039;t affect titles already installed.)&lt;br /&gt;
&lt;br /&gt;
This had a use after [[9.6.0-24|9.6.0-X]]: on a compromised 3DS running 9.2.0, you could install the 9.6.0 NATIVE_FIRM to FIRM0/FIRM1, but avoid putting it into the NATIVE_FIRM title.  This would boot the 9.2.0 system software but with the 9.6.0 Process9 and Kernel11.  With a user-mode exploit in a sufficiently-privileged application (e.g. mset), you could trigger a FIRM launch back to NATIVE_FIRM, which would load the 9.2.0 Process9 and Kernel11.&lt;br /&gt;
&lt;br /&gt;
9.6.0&#039;s keyslots 0x15 and 0x16 are unknown to 9.2.0, so 9.2.0 would not clear them.  You then could do firmlaunchhax against 9.2.0 to get ARM9 access with keyslots 0x15 and 0x16 set to their proper 9.6.0 values, allowing decrypting 9.6.0&#039;s encrypted titles.  Once the New3DS keystore was dumped, this became moot.&lt;br /&gt;
| Decrypting 9.6.0 NCCH files without dumping New3DS keystore&lt;br /&gt;
| None (but now moot)&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| August 12, 2018&lt;br /&gt;
| [[User:Yellows8|Yellows8]], [[User:Myria|Myria]]&lt;br /&gt;
|-&lt;br /&gt;
| FAT FS code null-deref&lt;br /&gt;
| When FSFile:Read is used with a file which is corrupted on a FAT filesystem(in particular SD), Process9 can crash. This particular crash is caused by a function returning NULL instead of an actual ptr due to an error. The caller of that function doesn&#039;t check for NULL which then triggers a read based at NULL.&lt;br /&gt;
&lt;br /&gt;
Sample &amp;quot;fsck.vfat -n -v -V &amp;lt;fat image backup&amp;gt;&amp;quot; output for the above crash:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;...&lt;br /&gt;
Starting check/repair pass.&lt;br /&gt;
&amp;lt;FilePath0&amp;gt; and&lt;br /&gt;
&amp;lt;FilePath1&amp;gt;&lt;br /&gt;
 share clusters.&lt;br /&gt;
 Truncating second to 3375104 bytes.&lt;br /&gt;
&amp;lt;FilePath1&amp;gt;&lt;br /&gt;
 File size is 2787392 bytes, cluster chain length is 16384 bytes.&lt;br /&gt;
 Truncating file to 16384 bytes.&lt;br /&gt;
Checking for unused clusters.&lt;br /&gt;
Reclaimed 1 unused cluster (16384 bytes).&lt;br /&gt;
Checking free cluster summary.&lt;br /&gt;
Free cluster summary wrong (1404490 vs. really 1404491)&lt;br /&gt;
 Auto-correcting.&lt;br /&gt;
Starting verification pass.&lt;br /&gt;
Checking for unused clusters.&lt;br /&gt;
Leaving filesystem unchanged.&amp;lt;/pre&amp;gt;&lt;br /&gt;
| Useless null-based-read&lt;br /&gt;
| None&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| July 8-9, 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[FS:EnumerateExtSaveData]] crashes process9 when trying to parse a file as an extdata directory in Data Management (MSET9)&lt;br /&gt;
| In the implementation for FSPXI:EnumerateExtSaveData (called by [[System_Settings|MSET]] to parse 3DS extdata IDs for Data Management), the return value of the P9 internal function call to open a directory (when enumerating contents of the extdata directory) was not checked. Therefore, if the call fails, an uninitialised pointer on stack will be used for a vtable call.&lt;br /&gt;
&lt;br /&gt;
As such, a file that starts with 8 hex digits can crash process9 if placed directly inside the extdata directory. It can crash in various ways based on subtle differences in the way the user triggers the crash event.&lt;br /&gt;
&lt;br /&gt;
While mostly leading to null derefs, in one specific context, process9 jumps directly to an ID1 string being held in ARM9 memory. Surprisingly, the 3DS doesn&#039;t discern what characters are used for the ID1 directory name on the SD, only requiring exactly 32 chars. This allows the attacker to insert arm instructions into the unicode ID1 dirname and take control of the ARM9, and thus, full control of the 3DS.&lt;br /&gt;
| ARM9 code execution (primary)&lt;br /&gt;
| None&lt;br /&gt;
| [[11.17.0-50|11.17.0-X]]&lt;br /&gt;
| April 2022&lt;br /&gt;
| August 7, 2023&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| RSA signature padding checks&lt;br /&gt;
| The TWL_FIRM RSA sig padding check code used for all TWL RSA sig-checks has issues, see [[FIRM|here]].&lt;br /&gt;
The main 3DS RSA padding check code(non-certificate, including NATIVE_FIRM) uses the function used with the above to extract more padding + the actual hash from the additional padding. This isn&#039;t really a problem here because there&#039;s proper padding check code which is executed prior to this.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.5.0-22|9.5.0-X]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[AMPXI:ValidateDSiWareSectionMAC]] [[AES_Registers|AES]] keyslot reuse&lt;br /&gt;
| When the input DSiWare section index is higher than &amp;lt;max number of DSiWare sections supported by this FIRM&amp;gt;, Process9 uses keyid 0x40 for calculating the AESMAC, which translates to keyslot 0x40. The result is that the keyslot is left at whatever was already selected before, since the AES selectkeyslot code will immediately  return when keyslot is &amp;gt;=0x40. However, actually exploiting this is difficult: the calculated AESMAC is never returned, this command just compares the calculated AESMAC with the input AESMAC(result-code depends on whether the AESMACs match). It&#039;s unknown whether a timing attack would work with this.&lt;br /&gt;
This is basically a different form of the pxips9 keyslot vuln, except with AESMAC etc.&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| March 15, 2015&lt;br /&gt;
| December 29, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| pxips9 [[AES_Registers|AES]] keyslot reuse&lt;br /&gt;
| This requires access to the [[Process_Services|ps:ps]]/pxi:ps9 services. One way to get access to this would be snshax on system-version &amp;lt;=10.1.0-X(see 32c3 3ds talk).&lt;br /&gt;
When an invalid key-type value is passed to any of the PS commands, Process9 will try to select keyslot 0x40. That aesengine_setkeyslot() code will then immediately return due to the invalid keyslot value. Since that function doesn&#039;t return any errors, Process9 will just continue to do crypto with whatever AES keyslot was selected before the PS command was sent.&lt;br /&gt;
| Reusing the previously used keyslot, for crypto with PS.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| Roughly the same time(same day?) as firmlaunch-hax.&lt;br /&gt;
| December 29, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| firmlaunch-hax: FIRM header ToCToU&lt;br /&gt;
| This can&#039;t be exploited from ARM11 userland.&lt;br /&gt;
During [[FIRM]] launch, the only FIRM header the ARM9 uses at all is stored in FCRAM, this is 0x200-bytes(the actual used FIRM RSA signature is read to the Process9 stack however). The ARM9 doesn&#039;t expect &amp;quot;anything&amp;quot; besides the ARM9 to access this data.&lt;br /&gt;
With [[9.5.0-22]] the address of this FIRM header was changed from a FCRAM address, to ARM9-only address 0x01fffc00.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[9.5.0-22]]&lt;br /&gt;
| &lt;br /&gt;
| 2012, 3 days after [[User:Yellows8|Yellows8]] started Process9 code RE.&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Uninitialized data output for (PXI) command replies&lt;br /&gt;
| PXI commands for various services(including some [[Filesystem_services_PXI|here]] and many others) can write uninitialized data (like from ARM registers) to the command reply. This happens with stubbed commands, but this can also occur with certain commands when returning an error.&lt;br /&gt;
Certain ARM11 service commands have this same issue as well.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.3.0-21|9.3.0-X]]&lt;br /&gt;
| ?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Filesystem_services_PXI|FSPXI]] OpenArchive SD permissions&lt;br /&gt;
| Process9 does not use the exheader ARM9 access-mount permission flag for SD at all.&lt;br /&gt;
This would mean ARM11-kernelmode code / fs-module itself could directly use FSPXI to access SD card without ARM9 checking for SD access, but this is rather useless since a process is usually running with SD access(Home Menu for example) anyway.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.3.0-21|9.3.0-X]]&lt;br /&gt;
| 2012&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[AMPXI:ExportDSiWare]] export path&lt;br /&gt;
| Process9 allocates memory on Process9 heap for the export path then verifies that the actual allocated size matches the input size. Then Process9 copies the input path from FCRAM to this buffer, and uses it with the Process9 FS openfile code, which use paths in the form of &amp;quot;&amp;lt;mountpoint&amp;gt;:/&amp;lt;path&amp;gt;&amp;quot;.&lt;br /&gt;
Process9 does not check the contents of this path at all before passing it to the FS code, besides writing a NUL-terminator to the end of the buffer.&lt;br /&gt;
| Exporting of DSiWare to arbitrary Process9 file-paths, such as &amp;quot;nand:/&amp;lt;path&amp;gt;&amp;quot; etc. This isn&#039;t really useful since the data which gets written can&#039;t be controlled.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.5.0-22]]&lt;br /&gt;
| April 2013&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[DSiWare_Exports]] [[CTCert]] verification&lt;br /&gt;
| Just like DSi originally did, 3DS verifies the APCert for DSiWare on SD with the CTCert also in the DSiWare .bin. On DSi this was fixed with with system-version 1.4.2 by verifying with the actual console-unique cert instead(stored in NAND), while on 3DS it&#039;s still not fixed.&lt;br /&gt;
On 3DS this is used in conjunction with seedminer to be able to decrypt &amp;amp; modify DSiWare TAD containers and inject them with exploitable DSiWare titles such as sudoku (sudokuhax) and Flipnote JPN (ugopwn)&lt;br /&gt;
| When the movable.sed keyY for the target 3DS is known and the target 3DS CTCert private-key is unknown, importing of modified DSiWare SD .bin files.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.10.0-X&lt;br /&gt;
| April 2013&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| seedminer: movable.sed keyY vulnerable to brute-force&lt;br /&gt;
| Half of the movable.sed keyY&#039;s 128 bits are leaked through the [[Nandrw/sys/LocalFriendCodeSeed_B|LFCS]], which is available in userland and below. The LFCS itself also leaks almost half of the remaining bits by following the ratio: u32 keyY[3]=1/5(LFCS). The remaining keyY[3] uncertainty of about ±2000 can be greatly reduced by plotting expected error margins with several keyYs. This results in a final uncertainty of about 2^40, easily within practical brute force range of an average modern PC.&lt;br /&gt;
| Knowing the keyY of a given 3ds allows for modification of DSiWare export contents, and chained with several other public vulns, ultimately arm9 execution.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.8.0-X&lt;br /&gt;
| December 2017&lt;br /&gt;
| January 2018&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| Improper validation of DSiWare title SRLs&lt;br /&gt;
| The 3DS does not verify if the actual SRL embedded in the title&#039;s directory matches the titleID in the TMD before launching it or importing it from an sd DSiWare export. &lt;br /&gt;
| This allows embedding older, exploitable DSiWare titles in completely different, unexploitable DSiWare titles. Since DSiWare has raw NAND RW, this can result in arm9 control through FIRM known-plaintext and sighax attacks.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.10.0-X&lt;br /&gt;
| 2015?&lt;br /&gt;
| December 2016&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| DSiWare import/export functions allow TWL system titles as arguments&lt;br /&gt;
| AM ImportTwlBackup/ExportTwlBackup unnecessarily allow TWL system titles such as DS Download Play to import/export from userland and System Settings -&amp;gt; Data Management (only am:sys is needed for userland). This is difficult to abuse for dsihax injection because no TWL system title has a save file, and any import with a save included will result in FS err C8804464. However, there is at least one dsihax primary that can load a payload from a non-NAND source, and not error if it can&#039;t access its public.sav (JPN Flipnote Studio v0).&lt;br /&gt;
| When combined with other public vulns, arm9 code execution.&lt;br /&gt;
| None.&lt;br /&gt;
| 11.10.0-X&lt;br /&gt;
| May 2018&lt;br /&gt;
| Sept 2018&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| [[Gamecard_Services_PXI]] unchecked REG_CTRCARDCNT transfer-size&lt;br /&gt;
| The u8 REG_CTRCARDCNT transfer-size parameter for the [[Gamecard_Services_PXI]] read/write CTRCARD commands is used as an index for an array of u16 values. Before [[5.0.0-11|5.0.0-X]] this u8 value wasn&#039;t checked, thus out-of-bounds reads could be triggered(which is rather useless in this case).&lt;br /&gt;
| Out-of-bounds read for a value which gets written to a register.&lt;br /&gt;
| [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| 2013?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[PXI_Registers|PXI]] cmdbuf buffer overrun&lt;br /&gt;
| The Process9 code responsible [[PXI_Registers|PXI]] communications didn&#039;t verify the size of the incoming command before writing it to a C++ member variable. &lt;br /&gt;
| Probably ARM9 code execution&lt;br /&gt;
| [[5.0.0-11|5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| March 2015, original timeframe if any unknown&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]]/[[User:Yellows8|Yellows8]]/maybe others(?)&lt;br /&gt;
|-&lt;br /&gt;
| [[Application_Manager_Services_PXI|PXIAM]]:ImportCertificates (See also [[Application_Manager_Services|this]])&lt;br /&gt;
| When handling this command, Process9 allocates a 0x2800-byte heap buffer, then copies the 4 FCRAM input buffers to this heap buffer without checking the sizes at all(only the buffers with non-zero sizes are copied). Starting with [[5.0.0-11|5.0.0-X]], the total combined size of the input data must be &amp;lt;=0x2800.&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| May 2013&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Process_Services_PXI|PS RSA]] commands buffer overflows&lt;br /&gt;
| pxips9 cmd1(not accessible via ps:ps) and VerifyRsaSha256: unchecked copy to a buffer in Process9&#039;s .bss, from the input FCRAM buffer. The buffer is located before the pxi cmdhandler threads&#039; stacks. SignRsaSha256 also has a buf overflow, but this isn&#039;t exploitable.&lt;br /&gt;
The buffer for this is the buffer for the signature data. With v5.0, the signature buffer was moved to stack, with a check for the signature data size. When the signature data size is too large, Process9 uses [[SVC|svcBreak]].&lt;br /&gt;
| ARM9 code execution&lt;br /&gt;
| [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[PXI_Registers|PXI]] pxi_id bad check&lt;br /&gt;
| The Process9 code responsible for [[PXI_Registers|PXI]] communications read pxi_id as a signed char. There were two flaws:&lt;br /&gt;
* They used it as index to a lookup-table without checking the value at all.&lt;br /&gt;
* Another function verified that pxi_id &amp;lt; 7, allowing negative values to pass the check. This would also cause an out-of-range table-lookup.&lt;br /&gt;
| Maybe ARM9 code execution&lt;br /&gt;
| [[3.0.0-5|3.0.0-5]]&lt;br /&gt;
|&lt;br /&gt;
| March 2015, originally 2012 for the first issue at least&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]], maybe others(?)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Kernel9 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[CONFIG Registers#CFG9_SYSPROT9|CFG9_SYSPROT9]] bit1 not set by Kernel9&lt;br /&gt;
| Old versions of Kernel9 never set bit1 of [[CONFIG Registers#CFG9_SYSPROT9|CFG9_SYSPROT9]]. This leaves the [[OTP Registers|0x10012000]]-region unprotected (this region should be locked early during boot!). Since it&#039;s never locked, you can dump it once you get ARM9 code execution.&lt;br /&gt;
&lt;br /&gt;
From [[3.0.0-5|3.0.0-X]] this was fixed by setting the bit in Kernel9 after poking some registers in that region. On New3DS arm9loader sets this bit instead of Kernel9, which is exploitable through a hardware + software vulnerability (see arm9loaderhax / description).&lt;br /&gt;
&lt;br /&gt;
This flaw resurged when it gained a new practical use: retrieving the OTP data for a New3DS console in order to decrypt the key data used in arm9loader (see enhanced-arm9loaderhax / description). This was performed by downgrading to a vulnerable system version. By accounting for differences in CTR-NAND crypto (0x05 -&amp;gt; 0x04, see partition encryption types [[Flash_Filesystem#NAND_structure|here]]) and using an Old3DS [[NCSD#NCSD_header|NCSD Header]], it is possible to boot a New3DS using Old3DS firmware 1.0-2.x to retrieve the required OTP data using this flaw.&lt;br /&gt;
| Dumping the [[OTP Registers|OTP]] area.&lt;br /&gt;
Decrypting New3DS sector 0x96 keyblock.&lt;br /&gt;
| [[3.0.0-5|3.0.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| February 2015&lt;br /&gt;
| [[User:Plutooo|plutoo]], Normmatt independently&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ARM11 software ==&lt;br /&gt;
=== Kernel11 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcUnbindInterrupt]] double free when irqId = 15&lt;br /&gt;
| svcBindInterrupt and svcUnbindInterrupt give special treatment to irqId 15 (FIQ helper): the access control list is bypassed and the provided KInterruptEvent (event or semaphore, via handle) is stored inside a singleton static object after having its refcount increased by 1.&lt;br /&gt;
&lt;br /&gt;
svcUnbindInterrupt assumes that the user-provided handle is what is stored in the singleton and will decref the user-provided KInterruptEvent twice, causing a use-after-free if the attacker didn&#039;t actually provide an handle to the same event or semaphore.&lt;br /&gt;
&lt;br /&gt;
This was &amp;quot;fixed&amp;quot; on [[11.14.0-46|11.14.0-X]] by preventing irqId 15 to be bound on retail units altogether (in both functions).&lt;br /&gt;
| Arm11 kernel code execution&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]] (only on retail units)&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2019&lt;br /&gt;
| [[User:TuxSH|TuxSH]], maybe others&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcKernelSetState]] op=3 could map the NULL page&lt;br /&gt;
| svcKernelSetState op=3 param1=1 maps the firmlaunch parameters page to the user-specified VA.&lt;br /&gt;
&lt;br /&gt;
It had previously no check, allowing the attacker to map data at VA 0.&lt;br /&gt;
&lt;br /&gt;
Starting from [[11.14.0-46|11.14.0-X]], the VA must be in the standard 0x10000000-0x14000000 address range.&lt;br /&gt;
| Mapping the NULL page (as RW) to leverage other kernel vulnerabilities&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2019&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcMapProcessMemory]] can map the NULL page&lt;br /&gt;
| svcMapProcessMemory&#039;s destination VA is unchecked.&lt;br /&gt;
&lt;br /&gt;
By passing a big enough &amp;quot;size&amp;quot; parameter, an attacker can map chunks of data at VA 0 in the destination (caller) process.&lt;br /&gt;
| Mapping the NULL page (as RW) to leverage other kernel vulnerabilities&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2020&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| Resource limit use-after-free&lt;br /&gt;
| When assigning a KResourceLimit to a KProcess, the reslimit&#039;s refcounter doesn&#039;t get incremented. This essentially means all KResourceLimit get freed if pm gets somehow terminated.&lt;br /&gt;
&lt;br /&gt;
It turns out it is possible to ask pm (via ns:s or pm:app) to terminate itself along all other KIPs simply by passing TID 0004000100001000.&lt;br /&gt;
&lt;br /&gt;
Calling [[SVC|svcGetResourceLimit]] afterwards triggers a use-after-free. This is rather difficult to exploit, however: there is one slot left in the reslimit slabheap. An attacker either has to map the NULL page as R(W)X (svcControlProcessMemory vuln fixed on [[11.8.0-41|11.8.0-X]]), or use one of the map-null exploits above while having access to svcCreateResourceLimit (with the only one that is easy enough to use in that context having been fixed on [[11.14.0-46|11.14.0-X]], anyway).&lt;br /&gt;
| Arm11 kernel code execution&lt;br /&gt;
| None (although near impossible to exploit on [[11.14.0-46|11.14.0-X]])&lt;br /&gt;
| [[11.14.0-46|11.14.0-X]]&lt;br /&gt;
| 2020&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcSetProcessIdealProcessor]] reference count overflow and therefore use-after-free.&lt;br /&gt;
| The SVC receive two arguments: handle and idealprocessor. The handle is used to get the KProcess object and the KProcess-&amp;gt;refCnt gets incremented,later the function check if the KProcess-&amp;gt;mem_type != BASE and if yes, it checks for idealprocessor == 2 or idealprocessor != 3. The problem here is that if you pass the idealprocessor = 3 it won&#039;t meet any condition and return the error 0xD9001BEA without decrement the reference count. &lt;br /&gt;
It can be abused to overflow the KProcess reference count that will lead to an Use-after-free. &lt;br /&gt;
| Before [[11.2.0-35|11.2.0-X]]: reference count overflow and therefore use-after-free.&lt;br /&gt;
| &lt;br /&gt;
| [[11.6.0-39|11.6.0-X]]&lt;br /&gt;
| November 2, 2017&lt;br /&gt;
| [[User:st4rk|st4rk]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcGetThreadList]] process reference leak&lt;br /&gt;
| When given a valid process handle (including &amp;lt;code&amp;gt;0xFFFF8001&amp;lt;/code&amp;gt;), svcGetThreadList forgets to decrement the reference count of the underlying [[KProcess]] instance, after having finished using it.&lt;br /&gt;
| Before [[11.2.0-35|11.2.0-X]]: reference count overflow and therefore use-after-free, but this UAF was most likely not exploitable&lt;br /&gt;
| &lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| April 3, 2017&lt;br /&gt;
| [[User:TuxSH|TuxSH]]&lt;br /&gt;
|-&lt;br /&gt;
| kernelhax via gspwn&lt;br /&gt;
| Originally the kernel didn&#039;t initialize [[CONFIG11_Registers#CFG11_GPUPROT|CFG11_GPUPROT]]. Since it&#039;s 0 at hard-boot, this allowed the GPU to access the entire FCRAM + AXIWRAM.&lt;br /&gt;
| Entire FCRAM+AXIWRAM R/W.&lt;br /&gt;
| [[3.0.0-5|3.0.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| February 7, 2017&lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]] partly&lt;br /&gt;
|-&lt;br /&gt;
| fasthax&lt;br /&gt;
| When a KTimer is created in pulse mode, the kernel calls a virtual function to reset the timer each time it pulses. The scheduler is locked for that core to avoid race conditions, but another core can call CloseHandle on the timer and free it, leading to a UAF vtable call.&lt;br /&gt;
| See description.&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| May 2016&lt;br /&gt;
| nedwill&lt;br /&gt;
|-&lt;br /&gt;
| ipctakeover&lt;br /&gt;
| When sending cmdreplies, it does not validate that the src_addr and src_size match the equivalent dst_addr and dst_size. With a modified addr/size specified in a cmdreply for an output buffer, the data-copy for the first/last pages could be used to overwrite data outside of the buffer specified by the original process.&lt;br /&gt;
&lt;br /&gt;
Used by ctr-httpwn as of v1.2, for &amp;quot;ipctakeover/bosshaxx&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This can be used to takeover processes where the process is using your service session. Like HTTPC -&amp;gt; BOSS, for bosshaxx above. NIM takeover can be done too(actual stack buffer overflow can trigger), etc.&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| November 26, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Using IPC input buffers as output buffers&lt;br /&gt;
| When sending cmdreplies, it does not validate that the cmdreply descriptor type matches the equivalent cmdreq descriptor type. This could be used by an exploited sysmodule to use what was intended as an input-buffer as an output-buffer, and also combine other IPC vuln(s) with this.&lt;br /&gt;
&lt;br /&gt;
Used by ctr-httpwn as of v1.2, for &amp;quot;ipctakeover/bosshaxx&amp;quot;.&lt;br /&gt;
| See description.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| November 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
|  [[SVC]] table too small&lt;br /&gt;
|  The table of function pointers for SVC&#039;s only contains entries up to 0x7D, but the biggest allowed SVC for the table is 0x7F. Thus, executing SVC7E or SVC7F would make the SVC-handler read after the buffer, and interpret some ARM instructions as function pointers.&lt;br /&gt;
&lt;br /&gt;
However, this would require patching the kernel .text or modifying SVC-access-control. Even if you could get these to execute, they would still jump to memory that isn&#039;t mapped as executable.&lt;br /&gt;
| &lt;br /&gt;
|  None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| 2012&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
|  [[SVC|svcBackdoor (0x7B)]]&lt;br /&gt;
|  This backdoor allows executing SVC-mode code at the user-specified code-address. This is used by Process9, using this on the ARM11 (with NATIVE_FIRM) required patching the kernel .text or modifying SVC-access-control.&lt;br /&gt;
| See description&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]] (deleted)&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
| Everyone&lt;br /&gt;
|-&lt;br /&gt;
| veryslowpidhax&lt;br /&gt;
| &#039;&#039;&#039;This is completely different from the kernelmode-code-execution vuln described in the below separate entry.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
When updating the kernel global PID counter under [[SVC|svcCreateProcess]] the kernel does not check for wraparound to 0x0(the PID for the very first process). This only matters because [[Services|SM-module]] allows processes with PID value less than &amp;lt;total ARM11 FIRM modules&amp;gt; to access &#039;&#039;all&#039;&#039; services, without checking exheader service-access-control; and because Kernel11 checks for the PID to be 1 (loader) to use the input mem-region value on ControlMemory. This alone does not affect access the [[SVC|SVCs]] access table at all.&lt;br /&gt;
&lt;br /&gt;
Inlined ldrex+strex code is used for updating the above counter. [[11.2.0-35|11.2.0-X]] had changes for similar code, but it was only for dedicated ldrex+strex functions(mainly for kernel objects) and hence this PID code was not affected.&lt;br /&gt;
&lt;br /&gt;
With launching+terminating a sysmodule repeatedly with this via ns:s, it would take weeks to finish(if not at least about a month?).&lt;br /&gt;
| Access to all [[Services_API|services]], ControlMemory on any given mem-region.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| 2012 maybe?&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
|  slowhax/waithax&lt;br /&gt;
|  svcWaitSynchronizationN does not decrement the references to valid handles in an array before returning an error when it encounters an invalid handle. This allows one to (slowly) overflow the reference count for a handle object to zero.&lt;br /&gt;
| ARM11 kernel-mode code execution&lt;br /&gt;
| [[11.2.0-35|11.2.0-X]]&lt;br /&gt;
| [[11.2.0-35|11.2.0-X]]&lt;br /&gt;
| 2016&lt;br /&gt;
| nedwill, [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Memory_layout#ARM11_Detailed_virtual_memory_map|0xEFF00000]] / 0xDFF00000 ARM11 kernel virtual-memory&lt;br /&gt;
| The ARM11 kernel-mode 0xEFF00000/0xDFF00000 virtual-memory(size 0x100000) is mapped to phys-mem 0x1FF00000(entire DSP-mem + entire AXIWRAM), with permissions RW-. This is used during ARM11 kernel startup for loading the FIRM-modules from the FIRM section located in DSP-mem, this never seems to be used after that, however. This is never unmapped either.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[11.3.0-36|11.3.0-X]]&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| memchunkhax2.1&lt;br /&gt;
| Nintendo&#039;s fix for memchunkhax2 in [[10.4.0-29|10.4.0-X]] did not fix the GPU case: one may cause the requisite ToCToU race using gspwn, bypassing the new validation.&lt;br /&gt;
derrek&#039;s original 32c3 presentation for memchunkhax2 commented that a GPU-based attack was possible, but would be difficult.  However, memchunkhax2.1 showed that it was possible to do fairly reliably.&lt;br /&gt;
| ARM11 kernel code execution&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]], via the new [[Memory_Management#MemoryBlockHeader|memchunkhdr]] MAC which prevents modifying memchunkhdr data with DMA.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| [[User:Derrek|derrek]], aliaspider&lt;br /&gt;
|-&lt;br /&gt;
| memchunkhax2&lt;br /&gt;
| When allocating a block of memory, the &amp;quot;next&amp;quot; pointer of the [[Memory_Management#MemoryBlockHeader|memchunkhdr]] is accessed without being checked after being mapped to userland.&lt;br /&gt;
This allows a race condition, where the process can change the next pointer just before it&#039;s accessed. By pointing the next pointer to a crafted memchunckhdr in the kernel SlabHeap, some of the SlabHeap is allocated to the calling process, allowing to change vtables of kernel objects. &lt;br /&gt;
| ARM11 kernel code execution&lt;br /&gt;
| [[10.4.0-29|10.4.0-X]] (partially, see memchunkhax2.1)&lt;br /&gt;
| [[10.4.0-29|10.4.0-X]]&lt;br /&gt;
|&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| heaphax&lt;br /&gt;
| Can change the size of free memchunk structures stored in FCRAM using DMA, which leads to the ability to allocate memory chunks over already-allocated memory. This can be used in the SYSTEM region to allocate RW memory over any part of the NS system module, which is enough to take it over.&lt;br /&gt;
| Code execution with access to all of NS&#039;s privileges. (including downgrading) Code execution within any applet.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]], via the new [[Memory_Management#MemoryBlockHeader|memchunkhdr]] MAC which prevents modifying memchunkhdr data with DMA.&lt;br /&gt;
| [[11.0.0-33|11.0.0-X]]&lt;br /&gt;
| April 2015 ?&lt;br /&gt;
| smea&lt;br /&gt;
|-&lt;br /&gt;
| snshax&lt;br /&gt;
| Can force creation of Safe NS process into gspwn-able memory, allowing for takeover.&lt;br /&gt;
| Code execution with access to all of NS&#039;s privileges. (including downgrading)&lt;br /&gt;
| [[10.1.0-27|10.1.0-X]]&lt;br /&gt;
| [[10.1.0-27|10.1.0-X]]&lt;br /&gt;
| April 2015 ?&lt;br /&gt;
| smea&lt;br /&gt;
|-&lt;br /&gt;
|  AffinityMask/processorid validation&lt;br /&gt;
|  With [[10.0.0-27|10.0.0-X]] the following functions were updated: svcGetThreadAffinityMask, svcGetProcessAffinityMask, svcSetProcessAffinityMask, and svcCreateThread. The code changes for all but svcCreateThread are identical.&lt;br /&gt;
The original code with the first 3 did the following: &lt;br /&gt;
* if(u32_processorcount &amp;gt; ~0x80000001)return 0xe0e01bfd;&lt;br /&gt;
* if(s32_processorcount &amp;gt; &amp;lt;total_cores&amp;gt;)return 0xd8e007fd;&lt;br /&gt;
The following code replaced the above:&lt;br /&gt;
* if(u32_processorcount &amp;gt;= &amp;lt;total_cores+1&amp;gt;)return 0xd8e007fd;&lt;br /&gt;
In theory the latter should catch everything that the former did, so it&#039;s unknown if this was really a security issue.&lt;br /&gt;
&lt;br /&gt;
The svcCreateThread changes with [[10.0.0-27|10.0.0-X]] definitely did fix a security issue.&lt;br /&gt;
* Original code: &amp;quot;if(s32_processorid &amp;gt; &amp;lt;total_cores&amp;gt;)return 0xd8e007fd;&amp;quot;&lt;br /&gt;
* New code: &amp;quot;if(s32_processorid &amp;gt;= &amp;lt;total_cores&amp;gt; || s32_processorid &amp;lt;= -4)return 0xd8e007fd;&amp;quot;&lt;br /&gt;
This fixed an off-by-one issue: if one would use processorid=total_cores, which isn&#039;t actually a valid value, svcCreateThread would accept that value on &amp;lt;[[10.0.0-27|10.0.0-X]]. This results in data being written out-of-bounds(baseaddr = arrayaddr + entrysize*processorid), which has the following result:&lt;br /&gt;
* Old3DS: Useless kernel-mode crash due to accessing unmapped memory.&lt;br /&gt;
* New3DS: uncontrolled data write into a kernel-mode L1 MMU-table. This isn&#039;t really useful: the data can&#039;t be controlled, and the data which gets overwritten is all-zero anyway(this isn&#039;t anywhere near MMU L1 entries for actually mapped memory).&lt;br /&gt;
The previous version also allowed large negative s32_processorid values(negative processorid values are special values not actual procids), but it appears using values like that won&#039;t actually do anything(meaning no crash) besides the thread not running / thread not running for a while(besides triggering a kernelpanic with certain s32_processorid value(s)).&lt;br /&gt;
| Nothing useful&lt;br /&gt;
|  [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| svcCreateThread issue: May 31, 2015. The rest: September 8, 2015, via v9.6-&amp;gt;v10.0 ARM11-kernel code-diff.&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| memchunkhax&lt;br /&gt;
| The kernel originally did not validate the data stored in the FCRAM kernel heap [[Memchunkhdr|memchunk-headers]] for free-memory at all. Exploiting this requires raw R/W access to these memchunk-headers, like physical-memory access with gspwn.&lt;br /&gt;
&lt;br /&gt;
There are &#039;&#039;multiple&#039;&#039; ways to exploit this, but the end-result for most of these is the same: overwrite code in AXIWRAM via the 0xEFF00000/0xDFF00000 kernel virtual-memory mapping.&lt;br /&gt;
&lt;br /&gt;
This was fixed in [[9.3.0-21|9.3.0-X]] by checking that the memchunk(including size, next, and prev ptrs) is located within the currently used heap memory. The kernel may also check that the next/prev ptrs are valid compared to other memchunk-headers basically. When any of these checks fail, kernelpanic() is called.&lt;br /&gt;
| When combined with other flaws: ARM11-kernelmode code execution&lt;br /&gt;
| [[9.3.0-21|9.3.0-21]]&lt;br /&gt;
| &lt;br /&gt;
| February 2014&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Multiple [[KLinkedListNode|KLinkedListNode]] SlabHeap use after free bugs&lt;br /&gt;
| The ARM11-kernel did access the &#039;key&#039; field of [[KLinkedListNode|KLinkedListNode]] objects, which are located on the SlabHeap, after freeing them. Thus, triggering an allocation of a new [[KLinkedListNode|KLinkedListNode]] object at the right time could result in a type-confusion. Pseudo-code:&lt;br /&gt;
SlabHeap_free(KLinkedListNode);&lt;br /&gt;
KObject *obj = KLinkedListNode-&amp;gt;key;  // the object there might have changed!&lt;br /&gt;
This bug appeared all over the place.&lt;br /&gt;
| ARM11-kernelmode code exec maybe&lt;br /&gt;
| [[8.0.0-18|8.0.0-18]]&lt;br /&gt;
| &lt;br /&gt;
| April 2015&lt;br /&gt;
| [[User:Derrek|derrek]]&lt;br /&gt;
|-&lt;br /&gt;
| PXI [[RPC_Command_Structure|Command]] input/output buffer permissions&lt;br /&gt;
| Originally the ARM11-kernel didn&#039;t check permissions for PXI input/output buffers for commands. Starting with [[6.0.0-11|6.0.0]] PXI input/output buffers must have RW permissions, otherwise kernelpanic is triggered.&lt;br /&gt;
| &lt;br /&gt;
| [[6.0.0-11|6.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcStartInterProcessDma]]&lt;br /&gt;
| For svcStartInterProcessDma, the kernel code had the following flaws:&lt;br /&gt;
&lt;br /&gt;
* Originally the ARM11-kernel read the input DmaConfig structure directly in kernel-mode(ldr(b/h) instructions), without checking whether the DmaConfig address is readable under userland. This was fixed by copying that structure to the SVC-mode stack, using the ldrbt instruction.&lt;br /&gt;
&lt;br /&gt;
* Integer overflows for srcaddr+size and dstaddr+size are now checked(with [[6.0.0-11]]), which were not checked before.&lt;br /&gt;
&lt;br /&gt;
* The kernel now also checks whether the srcaddr/dstaddr (+size) is within userland memory (0x20000000), the kernel now (with [[6.0.0-11]]) returns an error when the address is beyond userland memory. Using an address &amp;gt;=0x20000000 would result in the kernel reading from the process L1 MMU table, beyond the memory allocated for that MMU table(for vaddr-&amp;gt;physaddr conversion). &lt;br /&gt;
| &lt;br /&gt;
| [[6.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| DmaConfig issue: unknown. The rest: 2014&lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]] independently&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcControlMemory]] Parameter checks&lt;br /&gt;
| For svcControlMemory the parameter check had these two flaws:&lt;br /&gt;
&lt;br /&gt;
* The allowed range for addr0, addr1, size parameters depends on which MemoryOperation is being specified. The limitation for GSP heap was only checked if op=(u32)0x10003. By setting a random bit in op that has no meaning (like bit17?), op would instead be (u32)0x30003, and the range-check would be less strict and not accurate. However, the kernel doesn&#039;t actually use the input address for LINEAR memory-mapping at all besides the range-checks, so this isn&#039;t actually useful. This was fixed in the kernel by just checking for the LINEAR bit, instead of comparing the entire MemoryOperation value with 0x10003.&lt;br /&gt;
&lt;br /&gt;
* Integer overflows on (addr0+size) are now checked that previously weren&#039;t (this also applies to most other address checks elsewhere in the kernel).&lt;br /&gt;
&lt;br /&gt;
| &lt;br /&gt;
| [[5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
|&lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| [[RPC_Command_Structure|Command]] request/response buffer overflow&lt;br /&gt;
| Originally the kernel did not check the word-values from the command-header. Starting with [[5.0.0-11]], the kernel will trigger a kernelpanic() when the total word-size of the entire command(including the cmd-header) is larger than 0x40-words (0x100-bytes). This allows overwriting threadlocalstorage+0x180 in the destination thread. However, since the data written there would be translate parameters (such as header-words + buffer addresses), exploiting this would likely be very difficult, if possible at all.&lt;br /&gt;
&lt;br /&gt;
If the two words at threadlocalstorage+0x180 could be overwritten with controlled data this way, one could then use a command with a buffer-header of &amp;lt;nowiki&amp;gt;((size&amp;lt;&amp;lt;14) | 2)&amp;lt;/nowiki&amp;gt; to write arbitrary memory to any RW userland memory in the destination process.&lt;br /&gt;
| &lt;br /&gt;
| [[5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| v4.1 FIRM -&amp;gt; v5.0 code diff&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|SVC stack allocation overflows]]&lt;br /&gt;
| &lt;br /&gt;
* Syscalls that allocate a variable-length array on stack, only checked bit31 before multiplying by 4/16 (when calculating how much memory to allocate). If a large integer was passed as input to one of these syscalls, an integer overflow would occur, and too little memory would have been allocated on stack resulting in a buffer overrun. &lt;br /&gt;
* The alignment (size+7)&amp;amp;~7 calculation before allocation was not checked for integer overflow.&lt;br /&gt;
&lt;br /&gt;
This might allow for ARM11 kernel code-execution.&lt;br /&gt;
&lt;br /&gt;
(Applies to svcSetResourceLimitValues, svcGetThreadList, svcGetProcessList, svcReplyAndReceive, svcWaitSynchronizationN.)&lt;br /&gt;
| &lt;br /&gt;
| [[5.0.0-11]]&lt;br /&gt;
| &lt;br /&gt;
| v4.1 FIRM -&amp;gt; v5.0 code diff&lt;br /&gt;
| [[User:Plutooo|plutoo]], [[User:Yellows8|Yellows8]] complementary&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcControlMemory]] MemoryOperation MAP memory-permissions&lt;br /&gt;
| svcControlMemory with MemoryOperation=MAP allows mapping the already-mapped process virtual-mem at addr1, to addr0. The lowest address permitted for addr1 is 0x00100000. Originally the ARM11 kernel didn&#039;t check memory permissions for addr1. Therefore .text as addr1 could be mapped elsewhere as RW- memory, which allowed ARM11 userland code-execution.&lt;br /&gt;
| &lt;br /&gt;
| [[4.1.0-8]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[RPC_Command_Structure|Command]] input/output buffer permissions&lt;br /&gt;
| Originally the ARM11 kernel didn&#039;t check memory permissions for the input/output buffers for commands. Starting with [[4.0.0-7]] the ARM11 kernel will trigger a kernelpanic() if the input/output buffers don&#039;t have the required memory permissions. For example, this allowed a FSUSER file-read to .text, which therefore allowed ARM11-userland code execution.&lt;br /&gt;
| &lt;br /&gt;
| [[4.0.0-7]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SVC|svcReadProcessMemory/svcWriteProcessMemory memory]] permissions&lt;br /&gt;
| Originally the kernel only checked the first page(0x1000-bytes) of the src/dst buffers, for svcReadProcessMemory and svcWriteProcessMemory. There is no known retail processes which have access to these SVCs.&lt;br /&gt;
| &lt;br /&gt;
| [[4.0.0-7]]&lt;br /&gt;
| &lt;br /&gt;
| 2012?&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== [[FIRM]] Sysmodules ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in [[FIRM]] system version&lt;br /&gt;
!  Last [[FIRM]] system version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[Services|&amp;quot;srv:pm&amp;quot;]] process registration&lt;br /&gt;
| Originally any process had access to the port &amp;quot;srv:pm&amp;quot;. The PID&#039;s used for the (un)registration commands are not checked either. This allowed any process to re-register itself with &amp;quot;srv:pm&amp;quot;, and therefore allowed the process to give itself access to any service, bypassing the exheader service-access-control list.&lt;br /&gt;
&lt;br /&gt;
This was fixed in [[7.0.0-13]]: starting with [[7.0.0-13]] &amp;quot;srv:pm&amp;quot; is now a service instead of a globally accessible port. Only processes with PID&#039;s less than 6 (in other words: fs, ldr, sm, pm, pxi modules) have access to it. With [[7.0.0-13]] there can only be one session for &amp;quot;srv:pm&amp;quot; open at a time(this is used by pm module), svcBreak will be executed if more sessions are opened by the processes which can access this.&lt;br /&gt;
&lt;br /&gt;
This flaw was needed for exploiting the &amp;lt;=v4.x Process9 PXI vulnerabilities from ARM11 userland ROP, since most applications don&#039;t have access to those service(s).&lt;br /&gt;
| Access to arbitrary services&lt;br /&gt;
| [[7.0.0-13]]&lt;br /&gt;
| &lt;br /&gt;
| 2012&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| FSDIR null-deref&lt;br /&gt;
| [[Filesystem_services|FS]]-module may crash in some cases when handling directory reading. The trigger seems to be due to using [[FSDir:Close]] without closing the dir-handle afterwards?(Perhaps this is caused by out-of-memory?) This seems to be useless since it&#039;s just a null-deref.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| May 19(?)-20, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Useless [[SM]] off-by-one write&lt;br /&gt;
| After accepting a new session, [[SM]] writes a (handler ID (0 for srv: sessions (max. 64), 1 for the srv:pm one), pointer to session context structure in BSS) pair in a global array. However that array is only 64-entry-big instead of 65 (as it ought to be), and no bound check is done in that regard.&lt;br /&gt;
&lt;br /&gt;
Unfortunately, as of [[11.4.0-37]], the overwritten fields are totally unused after their initialization by &amp;lt;code&amp;gt;__libc_init_array&amp;lt;/code&amp;gt;.&lt;br /&gt;
| Not currently exploitable&lt;br /&gt;
| None&lt;br /&gt;
| [[11.4.0-37]]&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| smpwn&lt;br /&gt;
| When registering a new service (or &amp;quot;port&amp;quot;), no bound checks are done on the service table. One can simply call RegisterPort repeatedly to overflow that table: it will overflow into the command replay structure.&lt;br /&gt;
&lt;br /&gt;
Combined with a other minor bugs in the sysmodule, it is possible to take over [[SM]] with this nevertheless difficult-to-exploit vulnerability.&lt;br /&gt;
| Code execution under [[SM]], etc.&lt;br /&gt;
| [[11.16.0-48]]&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| July 2017&lt;br /&gt;
| [[User:TuxSH|TuxSH]] (independently), presumably ichfly before &lt;br /&gt;
|-&lt;br /&gt;
| PXI cmdbuf buffer overrun &lt;br /&gt;
| Like its Arm9 counterpart, before version [[5.0.0-11|5.0.0-X]], the PXI system module did not check the command sizes. This makes it possible to get ROP under the PXI sysmodule from a pwned Process9.&lt;br /&gt;
safecerthax uses it to takeover the Arm11 processor after directly getting remote code execution on the Arm9 side. Though, is useless in classic Arm11 -&amp;gt; Arm9 chains.&lt;br /&gt;
| ROP under [[PXI_Services|PXI]]&lt;br /&gt;
| probably [[5.0.0-11|5.0.0-X]]&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| &lt;br /&gt;
| Everyone&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Standalone Sysmodules ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in system-module system-version&lt;br /&gt;
!  Last system-module system-version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Timeframe this was added to wiki&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[CSND_Services|CSND]] sysmodule crash due to out of bounds parameters.&lt;br /&gt;
| The CSND command [[CSND:PlaySoundDirectly|PlaySoundDirectly (0x00040080)]] takes a channel ID as the first parameter. Any value outside the range [0-3] makes the system module become unstable or crash due to an out of bounds memory read. &lt;br /&gt;
| Out of bounds memory read, probably not exploitable. More research needed.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| January 2021&lt;br /&gt;
| January 22, 2021&lt;br /&gt;
| [[User:PabloMK7|PabloMK7]]&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| SSLoth: [[SSL_Services|SSL]] sysmodule improper certificate verification&lt;br /&gt;
| Initially, the SSL sysmodule missed the R_VERIFY_RES_SIGNATURE entry in the &amp;quot;resource list&amp;quot; provided to the RSA BSAFE library. Consequently, it did not check signatures when validating certificate chains. &lt;br /&gt;
| Forge fake certificates, spoof official servers and perform MitM attacks on SSL/TLS connections.&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| 2020&lt;br /&gt;
| December 18, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]], shutterbug2000 (independently)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD:ndm]] SetNZoneMacFilter (cmd8) stack smashing&lt;br /&gt;
| The length of the mac filter is not checked before being copied to a fixed-size buffer on stack.&lt;br /&gt;
| ROP under [[CECD_Services|CECD]] sysmodule&lt;br /&gt;
| None&lt;br /&gt;
| [[11.13.0-45]]&lt;br /&gt;
| 2020&lt;br /&gt;
| July 20, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] message box access &lt;br /&gt;
| CECD allows any process to write to any message box, thus allowing to write Streetpass data to the message box of any title.&lt;br /&gt;
| Install exploit for any title having a vulnerability in Streetpass data parsers (see CTRSDK Streetpass parser vulnerability).&lt;br /&gt;
| None&lt;br /&gt;
| None&lt;br /&gt;
| ?&lt;br /&gt;
| June 1, 2020&lt;br /&gt;
| Everyone?&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] packet type 0x32/0x34 stack-smashing&lt;br /&gt;
| When parsing Streetpass packets of type 0x32 and 0x34, CECD copies a list without checking the number of entries. The packet length is limited to 0x400 bytes, which is not enough to reach the end of the stack frame and overwrite the return address. However, the buffer located just next to the packet buffer is actually filled with data sent just before, hence actually allowing to overwrite the whole stack frame with conrolled data.&lt;br /&gt;
| RCE under [[CECD_Services|CECD]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| Summer 2019&lt;br /&gt;
| June 1, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] TMP files parser multiple vulnerabilities&lt;br /&gt;
| When parsing &amp;quot;TMP_XXX&amp;quot; files, CECD does not check the number of messages contained in the file. This allows to overflow the array of message pointers and message sizes on the stack. Pointers aren&#039;t controlled and sizes are limited (one cannot send gigabytes of data...), yet the last message size can be an arbitrary value (the current message pointer goes outside the file buffer and the parsing loop is broken). This allows to overwrite a pointer to a lock object on the stack and decrement an arbitrary value in memory. One can change the TMP file parsing mode to have CECD trying to free all the message buffers after parsing the next TMP file. The parsing mode is usually restored when parsing a new TMP file, but an invalid TMP file allows to make a function returns an error before the mode is restored , the return value is not checked and the parser consider the file valid. The message pointers and sizes arrays are not updated though, this is not a problem since the previous TMP file buffer is reused for the new TMP file in memory. Thus the message pointers actually points to controlled data. This allows to get a bunch of fake heap chunk freed, thus a bunch of unsafe unlink arbitrary writes.&lt;br /&gt;
| RCE under [[CECD_Services|CECD]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| Summer 2019&lt;br /&gt;
| June 1, 2020&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[Config_Services|CFG]]:CreateConfigInfoBlk integer underflow&lt;br /&gt;
| When creating a new block it checks the size of the block is &amp;lt;= 0x8000, but it doesn&#039;t check that the block size is less than the remaining space. This induces an integer underflow (remaining_space-block_size), the result is then used for another check (buf_start+current_offset+constant &amp;lt;= remaining_space-block_size) and then in a mempcy call (dest = buf_start+(u16)(remaining_space-block_size), size =block_size). This allow for writing past the buffer, however because of the u16 cast in the memcpy call memory has to be mapped from buf_start to buf_start+0x10000 (cannot write backward).&lt;br /&gt;
| Theoritically ROP under CFG services, but BSS section is to small (size &amp;lt;= 0x10000) so it only results in a crash.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.8.0-41]]&lt;br /&gt;
| November, 2018&lt;br /&gt;
| November 24, 2018&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[MP:SendDataFrame]] missing input array index validation&lt;br /&gt;
| [[MP:SendDataFrame]] doesn&#039;t validate the input index at cmdreq[1], unless the function for flag=non-zero is executed. This is used to calculate the following, without validating the index at all: someptr = stateptr + (index*0x924) + somestateoffset.&lt;br /&gt;
&lt;br /&gt;
After validating some flags from someptr, when input_flag=0 the input buffer data is copied to someptr+someotheroffset+0x14 with the u16 size loaded from someptr+someotheroffset.&lt;br /&gt;
&lt;br /&gt;
With a large input index someptr could be setup to be at a &amp;lt;target address&amp;gt;, for overwriting memory.&lt;br /&gt;
&lt;br /&gt;
This is probably difficult to exploit.&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[8.0.0-18]](MP-sysmodule v2048)&lt;br /&gt;
| January 22, 2017&lt;br /&gt;
| January 22, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[MP_Services|MP]] cmd1 out-of-bounds handle read&lt;br /&gt;
| MP-sysmodule handles the input parameter for cmd1 as a s32. It checks for &amp;gt;=16, but not &amp;lt;0. With &amp;lt;16 it basically does the following(array of entries 4-bytes each): *outhandle = ((Handle*)(stateptr+offsetinstate))[inputindex].&lt;br /&gt;
&lt;br /&gt;
Hence, this can be used to load any handle in MP-sysmodule memory. MP doesn&#039;t really have any service handles of interest however(can be obtained from elsewhere too).&lt;br /&gt;
| Reading any handle in MP-sysmodule memory.&lt;br /&gt;
| None&lt;br /&gt;
| [[8.0.0-18]](MP-sysmodule v2048)&lt;br /&gt;
| January 21, 2017&lt;br /&gt;
| January 22, 2017&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| AM stack/.bss infoleak via [[AM:ReadTwlBackupInfo]]([[AM:ReadTwlBackupInfoEx|Ex]])&lt;br /&gt;
| After writing the output-info structure to stack, it then copies that structure to the output buffer ptr using the size from the command. The size is not checked. This could be used to read data from the AM-service-thread stack handling the command + .bss.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;This was not tested on hardware.&#039;&#039;&#039;&lt;br /&gt;
| Stack/.bss reading&lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27]](AM v9217)&lt;br /&gt;
| Roughly October 17, 2016&lt;br /&gt;
| October 25, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| AM module APcert infoleak via 00000000.ctx files&lt;br /&gt;
| Just after a download title is purchased from the eShop, the .ctx is in an initialized state of all FFs past the header. During download, the FF area is filled with the console APcert. Thus, it is possible to create a xorpad from the initial state and use it to decrypt the APcert filled state.&lt;br /&gt;
| APcert contains the deviceID, which can beneficial in decrypting the movable.sed (since deviceID is mathmatically related to the LFCS).&lt;br /&gt;
| None&lt;br /&gt;
| [[11.16.0-49]]&lt;br /&gt;
| August, 2022&lt;br /&gt;
| March 17, 2023&lt;br /&gt;
| zoogie&lt;br /&gt;
|-&lt;br /&gt;
| [[MVD_Services|MVD]]: Stack buffer overflow with [[MVDSTD:SetupOutputBuffers]].&lt;br /&gt;
| The input total_entries is not validated when initially processing the input entry-list. This fixed-size input entry-list is copied to stack from the command request. The loop for processing this initializes a global table, the converted linearmem-&amp;gt;physaddrs used there are also copied to stack(0x8-bytes of physaddrs per entry).&lt;br /&gt;
&lt;br /&gt;
If total_entries is too large, MVD-sysmodule will crash due to reading unmapped memory following the stack(0x10000000). Afterwards if the out-of-bounds total_entries is smaller than that, it will crash due accessing address 0x0, hence this useless.&lt;br /&gt;
| MVD-sysmodule crash.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.0.0-20]]&lt;br /&gt;
| April 22, 2016 (Tested on the 25th)&lt;br /&gt;
| April 25, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWM_Services|NWM]]: Using CTRSDK heap with UDS sharedmem from the user-process.&lt;br /&gt;
| See the HTTP-sysmodule section below.&lt;br /&gt;
&lt;br /&gt;
CTRSDK heap is used with the sharedmem from [[NWMUDS:InitializeWithVersion]]. Buffers are allocated/freed under this heap using [[NWMUDS:Bind]] and [[NWMUDS:Unbind]].&lt;br /&gt;
&lt;br /&gt;
Hence, overwriting sharedmem with gspwn then using [[NWMUDS:Unbind]] results in the usual controlled CTRSDK memchunk-header write, similar to HTTP-sysmodule.&lt;br /&gt;
&lt;br /&gt;
This could be done by creating an UDS network, without any other nodes on the network.&lt;br /&gt;
&lt;br /&gt;
Besides CTRSDK memchunk-headers, there are no addresses stored under this sharedmem.&lt;br /&gt;
| ROP under NWM-module.&lt;br /&gt;
| None (need to check, but CTRSDK heap code is vulnerable)&lt;br /&gt;
| [[9.0.0-20|9.0.0-X]]&lt;br /&gt;
| April 10, 2016&lt;br /&gt;
| April 16, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[DLP_Services|DLP]]: Out-of-bounds memory access during spectator [[Download_Play|data-frame]] checksum calculation&lt;br /&gt;
| DLP doesn&#039;t validate the frame_size when receiving spectator data-frames at all, unlike non-spectator data-frames. The actual spectator data-frame parsing code doesn&#039;t use that field either. However, the data-frame checksum calculation code called during checksum verification does use the frame_size for loading the size of the framebuf.&lt;br /&gt;
&lt;br /&gt;
Hence, using a large frame_size like 0xFFFF will result in the checksum calculation code reading data out-of-bounds. This isn&#039;t really useful, you could trigger a remote local-WLAN DLP-sysmodule crash while a 3DS system is scanning for DLP networks(due to accessing unmapped memory), but that&#039;s about all(trying to infoleak with this likely isn&#039;t useful either).&lt;br /&gt;
| DLP-sysmodule crash, handled by dlplay system-application by a &amp;quot;connection interrupted&amp;quot; error eventually then a fatal-error via ErrDisp.&lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| April 8, 2016 (Tested on the 10th)&lt;br /&gt;
| April 10, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[DLP_Services|DLP]]: Out-of-bounds output data writing during spectator sysupdate titlelist [[Download_Play|data-frame]] handling&lt;br /&gt;
| The total_entries and out_entryindex fields for the titlelist DLP spectator data-frames are not validated. This is parsed during DLP network scanning. Hence, the specified titlelist data can be written out-of-bounds using the specified out_entryindex and total_entries. A crash will occur while reading the input data-frame titlelist if total_entries is larger than 0x27A, due to accessing unmapped memory.&lt;br /&gt;
&lt;br /&gt;
There&#039;s not much non-zero data to overwrite following the output buffer(located in sharedmem), any ptrs are located in sharedmem. Overwriting certain ptr(s) are only known to cause a crash when attempting to use the DLP-client shutdown service-command.&lt;br /&gt;
&lt;br /&gt;
There&#039;s no known way to exploit the above crash, since the linked-list code involves writes zeros(with a controlled start ptr).&lt;br /&gt;
| &lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| April 8-9, 2016&lt;br /&gt;
| April 10, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[IR_Services|IR]]: Stack buffer overflow with custom hardware&lt;br /&gt;
| Originally IR sysmodule used the read value from the I2C-IR registers TXLVL and RXLVL without validating them at all. See [[10.6.0-31|here]] for the fix. This is the size used for reading the data-recv FIFO, etc. The output buffer for reading is located on the stack.&lt;br /&gt;
&lt;br /&gt;
This should be exploitable if one could successfully setup the custom hardware for this and if the entire intended sizes actually get read from I2C.&lt;br /&gt;
| ROP under IR sysmodule.&lt;br /&gt;
| [[10.6.0-31|10.6.0-31]]&lt;br /&gt;
| &lt;br /&gt;
| February 23, 2016 (Unknown if it was noticed before then)&lt;br /&gt;
| February 23, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[HTTP_Services|HTTP]]: Using CTRSDK heap with sharedmem from the user-process.&lt;br /&gt;
| The data from httpcAddPostDataAscii and other commands is stored under a CTRSDK heap. That heap is the sharedmem specified by the user-process via the HTTPC Initialize command.&lt;br /&gt;
Normally this sharedmem isn&#039;t accessible to the user-process once the sysmodule maps it, hence using it is supposed to be &amp;quot;safe&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
This isn&#039;t the case due to gspwn however. Since CTRSDK heap code is so insecure in general, one can use gspwn to locate the HTTPC sharedmem + read/write it, then trigger a mem-write under the sysmodule. This can then be used to get ROP going under HTTP-sysmodule.&lt;br /&gt;
&lt;br /&gt;
This is exploited by [https://github.com/yellows8/ctr-httpwn/ctr-httpwn ctr-httpwn].&lt;br /&gt;
| ROP under HTTP sysmdule.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.13.0-45|11.13.0-X]]&lt;br /&gt;
| Late 2015&lt;br /&gt;
| March 22, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NIM_Services|NIM]]: Downloading old title-versions from eShop&lt;br /&gt;
| Multiple NIM service commands(such as [[NIMS:StartDownload]]) use a title-version value specified by the user-process, NIM does not validate that this input version matches the latest version available via SOAP. Therefore, when combined with AM(PXI) [[#Process9|title-downgrading]] via deleting the target eShop title with System Settings Data Management(if the title was already installed), this allows downloading+installing any title-version from eShop &#039;&#039;if&#039;&#039; it&#039;s still available from CDN.&lt;br /&gt;
The easiest way to exploit this is to just patch the eShop system-application code using these NIM commands(ideally the code which loads the title-version).&lt;br /&gt;
&lt;br /&gt;
Originally this was tested with a debugging-system via modded-FIRM, eventually smea implemented it in HANS for the 32c3 release.&lt;br /&gt;
| Downloading old title-versions from eShop&lt;br /&gt;
| None&lt;br /&gt;
| [[10.0.0-27|10.0.0-X]]&lt;br /&gt;
| October 24, 2015 (Unknown when exactly the first eShop title downgrade was actually tested, maybe November)&lt;br /&gt;
| January 7, 2016 (Same day Ironfall v1.0 was removed from CDN via the main-CXI files)&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[SPI_Services|SPI]] service out-of-bounds write&lt;br /&gt;
| cmd1 has out-of-bounds write allowing overwrite of some static variables in .data.&lt;br /&gt;
| Code execution under spi sysmodule; access to [[CONFIG11_Registers|CFG11_GPUPROT]] and ultimately kernel code execution. &lt;br /&gt;
| None&lt;br /&gt;
| [[11.14.0-46]]&lt;br /&gt;
| March 2015&lt;br /&gt;
| &lt;br /&gt;
| [[User:Plutooo|plutoo]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NFC_Services|NFC]] module service command buf-overflows&lt;br /&gt;
| NFC module copies data with certain commands, from command input buffers to stack without checking the size. These commands include the following, it&#039;s unknown if there&#039;s more commands with similar issues: &amp;quot;nfc:dev&amp;quot; &amp;lt;0x000C....&amp;gt; and &amp;quot;nfc:s&amp;quot; &amp;lt;0x0037....&amp;gt;.&lt;br /&gt;
Since both of these commands are stubbed in the Old3DS NFC module from the very first version(those just return an error), these issues only affect the New3DS NFC module.&lt;br /&gt;
&lt;br /&gt;
There&#039;s no known retail titles which have access to either of these services.&lt;br /&gt;
| ROP under NFC module.&lt;br /&gt;
| New3DS: None&lt;br /&gt;
| New3DS: [[9.5.0-22]]&lt;br /&gt;
| December 2014?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[News_Services|NEWSS]] service command notificationID validation failure&lt;br /&gt;
| This module does not validate the input notificationID for &amp;lt;nowiki&amp;gt;&amp;quot;news:s&amp;quot;&amp;lt;/nowiki&amp;gt; service commands. This is an out-of-bounds array index bug. For example, [[NEWSS:SetNotificationHeader]] could be used to exploit news module: this copies the input data(size is properly checked) to: out = newsdb_savedata+0x10 + (someu32array[notificationID]*0x70).&lt;br /&gt;
| ROP under news module.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.7.0-25|9.7.0-X]]&lt;br /&gt;
| December 2014&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWMUDS:DecryptBeaconData]] heap buffer overflow&lt;br /&gt;
| input_size = 0x1E * &amp;lt;value the u8 from input_[[NWM_Services|networkstruct]]+0x1D&amp;gt;. Then input_tag0 is copied to a heap buffer. When input_size is larger than 0xFA-bytes, it will then copy input_tag1 to &amp;lt;end_address_of_previous_outbuf&amp;gt;, with size=input_size-0xFA.&lt;br /&gt;
&lt;br /&gt;
This can be triggered by either using this command directly, or by boadcasting a wifi beacon which triggers it while a 3DS system running the target process is in range, when the process is scanning for hosts to connect to. Processes will only pass tag data to this command when the wlancommID and other thing(s) match the values for the process.&lt;br /&gt;
&lt;br /&gt;
There&#039;s no known way to actually exploit this for getting ROP under NWM-module, at the time of originally adding this to the wiki. This is because the data which gets copied out-of-bounds *and* actually causes crash(es), can&#039;t be controlled it seems(with just broadcasting a beacon at least). It&#039;s unknown whether this could be exploited from just using NWMUDS service-cmd(s) directly.&lt;br /&gt;
| Without any actual way to exploit this: NWM-module DoS, resulting in process termination(process crash). This breaks *everything* involving wifi comms, a reboot is required to recover from this.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.0.0-20]]&lt;br /&gt;
| ~September 23, 2014(see the [[NWMUDS:DecryptBeaconData]] page history)&lt;br /&gt;
| August 3, 2015&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[HID_Services|HID]] module shared-mem&lt;br /&gt;
| HID module does not validate the index values in [[HID_Shared_Memory|sharedmem]](just changes index to 0 when index == maxval when updating), therefore large values will result in HID module writing HID data to arbitrary addresses.&lt;br /&gt;
| ROP under HID module, but this is *very* unlikely to be exploitable since the data written is HID data.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.3.0-21]]&lt;br /&gt;
| 2014?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| gspwn&lt;br /&gt;
| GSP module does not validate addresses given to the GPU. This allows a user-mode application/applet to read/write to a large part of physical FCRAM using GPU DMA. From this, you can overwrite the .text segment of the application you&#039;re running under, and gain real code-execution from a ROP-chain. Normally applets&#039; .text([[Home Menu]], [[Internet Browser]], etc) is located beyond the area accessible by the GPU, except for [[RO_Services|CROs]] used by applets([[Internet Browser]] for example).&lt;br /&gt;
&lt;br /&gt;
FCRAM is gpu-accessible up to physaddr 0x26800000 on Old3DS, and 0x2D800000 on New3DS. This is BASE_memregion_start(aka SYSTEM_memregion_end)-0x400000 (0x800000 with New3DS) with the default memory-layout on Old3DS/New3DS. With [[11.3.0-36|11.3.0-X]] the cutoff now varies due to the new [[SVC]] 0x59. The New3DS &amp;quot;normal&amp;quot;(non-APPLICATION) cutoff was changed to 0x2D000000 due to the new [[SVC]] 0x59.&lt;br /&gt;
| User-mode code execution.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.6.0-24|9.6.0-X]]&lt;br /&gt;
| Early 2014&lt;br /&gt;
| &lt;br /&gt;
| smea, [[User:Yellows8|Yellows8]]/others before then&lt;br /&gt;
|-&lt;br /&gt;
| [[GSP_Services|GSP]] client management failures&lt;br /&gt;
| Shared memory of GSP clients is all on the same page, this allows any GSP client to craft custom GX commands for other clients.&lt;br /&gt;
&lt;br /&gt;
When a process with memtype != APPLICATION acquires rights, the FCRAM cutoff is increased to 0x26800000 (O3DS) / 0x2D000000 (N3DS). Meanwhile, [[GSPGPU:TriggerCmdReqQueue]] does not check if the calling client has rendering rights. This allows any process to access most of the SYSTEM region by crafting DMA commands for the [[Home Menu]] (a non-APPLICATION process which is always a client of GSP) and jumping to it so that GSP updates the cutoff, while triggering queue processing from a separate thread.&lt;br /&gt;
| Access to most of the SYSTEM memory region.&lt;br /&gt;
| None&lt;br /&gt;
| [[11.17.0-50|11.17.0-50]]&lt;br /&gt;
|&lt;br /&gt;
| May 2025&lt;br /&gt;
| [[User:kynex7510|kynex7510]], probably others&lt;br /&gt;
|-&lt;br /&gt;
| rohax&lt;br /&gt;
| Using gspwn, it is possible to overwrite a loaded [[CRO0]]/[[CRR0]] after its RSA-signature has been validated. Badly validated [[CRO0]] header leads to arbitrary read/write of memory in the ro-process. This gives code-execution in the ro module, who has access to [[SVC|syscalls]] 0x70-0x72, 0x7D.&lt;br /&gt;
&lt;br /&gt;
This was fixed after [[ninjhax]] release by adding checks on [[CRO0]]-based pointers before writing to them.&lt;br /&gt;
| Memory-mapping syscalls.&lt;br /&gt;
| [[9.3.0-21]]&lt;br /&gt;
| [[9.4.0-21]]&lt;br /&gt;
| &lt;br /&gt;
| &lt;br /&gt;
| smea, [[User:Plutooo|plutoo]] joint effort&lt;br /&gt;
|-&lt;br /&gt;
| Region free&lt;br /&gt;
| Only [[Home Menu]] itself checks gamecards&#039; region when launching them. Therefore, any application launch that is done directly with [[NS]] without signaling Home Menu to launch the app, will result in region checks being bypassed.&lt;br /&gt;
This essentially means launching the gamecard with the [[NS_and_APT_Services|&amp;quot;ns:s&amp;quot;]] service. The main way to exploit this is to trigger a FIRM launch with an application specified, either with a normal FIRM launch or a hardware [[NSS:RebootSystem|reboot]].&lt;br /&gt;
| Launching gamecards from any region + bypassing Home Menu gamecard-sysupdate installation&lt;br /&gt;
| None&lt;br /&gt;
| Last tested with [[10.1.0-27|10.1.0-X]].&lt;br /&gt;
| June(?) 2014&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWM_Services|NWM]] service-cmd state null-ptr deref&lt;br /&gt;
| The NWMUDS service command code loads a ptr from .data, adds an offset to that, then passes that as the state address for the actual command-handler function. The value of the ptr loaded from .data is not checked, therefore this will cause crashes due to that being 0x0 when NWMUDS was not properly initialized.&lt;br /&gt;
It&#039;s unknown whether any NWM services besides NWMUDS have this issue.&lt;br /&gt;
| This is rather useless since it&#039;s only a crash caused by a state ptr based at 0x0.&lt;br /&gt;
| None&lt;br /&gt;
| [[9.0.0-20]]&lt;br /&gt;
| 2013?&lt;br /&gt;
| &lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== General/CTRSDK ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Summary&lt;br /&gt;
!  Description&lt;br /&gt;
!  Successful exploitation result&lt;br /&gt;
!  Fixed in version&lt;br /&gt;
!  Last version this flaw was checked for&lt;br /&gt;
!  Timeframe this was discovered&lt;br /&gt;
!  Discovered by&lt;br /&gt;
|-&lt;br /&gt;
| [[CECD_Services|CECD]] Streetpass message exheader stack-smashing&lt;br /&gt;
| When parsing streetpass messages, &amp;quot;nn::cec::CTR::Message::InputMessage&amp;quot; calls &amp;quot;nn::cec::CTR::Message::SetExHeaderWithoutCalc&amp;quot; for each exheader entry in the input message. The number of entries should not exceed 16 but remains unchecked, leading to a stack-buffer-overflow.&lt;br /&gt;
| ROP under any application parsing Streetpass messages&lt;br /&gt;
Remote code execution under [[CECD_Services|CECD]]&lt;br /&gt;
| [[11.12.0-44]]&lt;br /&gt;
| &lt;br /&gt;
| 2019&lt;br /&gt;
| [[User:Nba_Yoh|MrNbaYoh]]&lt;br /&gt;
|-&lt;br /&gt;
| [[NWM_Services|UDS]] beacon additional-data buffer overflow&lt;br /&gt;
| Originally CTRSDK did not validate the UDS additional-data size before using that size to copy the additional-data to a [[NWM_Services|networkstruct]]. This was eventually fixed.&lt;br /&gt;
This was discovered while doing code RE with an old dlp-module version. It&#039;s unknown in what specific CTRSDK version this was fixed, or even what system-version updated titles with a fixed version.&lt;br /&gt;
&lt;br /&gt;
It&#039;s unknown if there&#039;s any titles using a vulnerable CTRSDK version which are also exploitable with this(dlp module can&#039;t be exploited with this).&lt;br /&gt;
&lt;br /&gt;
The maximum number of bytes that can be written beyond the end of the outbuf is 0x37-bytes, with additionaldata_size=0xFF.&lt;br /&gt;
| Perhaps ROP, very difficult if possible with anything at all&lt;br /&gt;
| ?&lt;br /&gt;
| &lt;br /&gt;
| September(?) 2014&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| CTPK buffer overflow&lt;br /&gt;
| At offset 0x20 in CTPK is an array for each texture, each entry is 0x20-bytes. This contains a wordindex(entry+0x18) for some srcdata relative to CTPK+0, and an u8 wordsize(entry+0x14) for this data. The CTRSDK function handling this doesn&#039;t validate the size, when copying srcdata using this size to the output buffer. Applications usually have the output buffer on the stack, hence stack buffer overflow.&lt;br /&gt;
&lt;br /&gt;
While CTPK(*.ctpk) are normally only loaded from RomFS, some application(s) load from elsewhere too.&lt;br /&gt;
| ROP under the target application.&lt;br /&gt;
| None?&lt;br /&gt;
| &amp;quot;[SDK+NINTENDO:CTR_SDK-11_4_0_200_none]&amp;quot;&lt;br /&gt;
| November 14, 2016&lt;br /&gt;
| [[User:Yellows8|Yellows8]]&lt;br /&gt;
|-&lt;br /&gt;
| Pia vulns&lt;br /&gt;
| [https://switchbrew.org/wiki/Switch_System_Flaws#Pia Originally discovered in Pia v5.x for Switch], these vulns are also present in earlier versions (v3.x/4.x/5.x, possibly earlier?) for 3DS (and Wii U too).&lt;br /&gt;
Pia encryption generally wasn&#039;t used pre-Switch (sent packets are plaintext). 3DS is affected by all Pia vulns listed above except for LAN. The functionality for ParseLeaveMeshInvitation doesn&#039;t exist in 3DS Pia v3.9.2. Wii U is affected by all listed Pia vulns except for the LAN vulns.&lt;br /&gt;
| See [https://switchbrew.org/wiki/Switch_System_Flaws#Pia here].&lt;br /&gt;
| Unfixed on 3DS/Wii U&lt;br /&gt;
| &amp;quot;[SDK+Nintendo:PIA_5_4_3]&amp;quot;&lt;br /&gt;
| See [https://switchbrew.org/wiki/Switch_System_Flaws#Pia here]; separately checked later (UpdateConnectionReport) by [[User:Riley|Riley]] on: June 14, 2023&lt;br /&gt;
| [[User:Yellows8|Yellows8]]; added to 3dbrew (UpdateConnectionReport) by [[User:Riley|Riley]] later&lt;br /&gt;
|-&lt;br /&gt;
| pialease nerf: stack overflow in Pia when parsing UDS packet cmd=5 &amp;quot;UpdateMigrationNodeInfoMessage&amp;quot;&lt;br /&gt;
| A UDS packet as received by Pia contains a command type, where cmd=1 is higher-layer game-data, and other cmds are parsed internally.&lt;br /&gt;
&lt;br /&gt;
A function named &amp;quot;UdsNode::ParseUpdateMigrationNodeInfoMessage&amp;quot; is called to handle packets with cmd=5.&lt;br /&gt;
&lt;br /&gt;
This checks the player nodeID (returns if not player 1, that is, UDS network host), then calls an additional function which does a loop of 64-bit copies to a fixed-size stack buffer using unchecked index and data from the received packet contents.&lt;br /&gt;
&lt;br /&gt;
This therefore leads to trivial RCE (of every UDS network client) by just sending a single UDS packet; only 0xC u64s on stack can be overwritten easily, but just 2 is enough to start a ROP chain and pivot to the rest of the UDS packet contents elsewhere on the stack.&lt;br /&gt;
&lt;br /&gt;
To exploit some games, an attacker would need to also reimplement the DLP server protocol (and any quirks that game has when parsing the UDS network passphrase obtained from the DLP server). One game that requires this is Mario Party: Island Tour (only the dlplay child connects to a UDS network).&lt;br /&gt;
&lt;br /&gt;
Earliest version of Pia known to be vulnerable is v2.x. v1.x still parses this packet, but does not have the stack-copy loop (index is still unchecked there leading to heap overflow but due to overwrites not being contiguous in memory it may or may not be exploitable).&lt;br /&gt;
&lt;br /&gt;
Fixed with Pia version 4.x, which refactored the UDS send/receive wrapper code and parses completely different commands.&lt;br /&gt;
| ROP under the vulnerable application. A server can exploit every client connected to it; a client can exploit every other client connected to that server.&lt;br /&gt;
| &amp;quot;[SDK+Nintendo:PIA_4_2_0]&amp;quot;&lt;br /&gt;
| &amp;quot;[SDK+Nintendo:PIA_3_10_2]&amp;quot;, &amp;quot;[SDK+Nintendo:PIA_4_2_0]&amp;quot;&lt;br /&gt;
| Discovery: June 3, 2023.&lt;br /&gt;
&lt;br /&gt;
Wiki: November 20, 2023.&lt;br /&gt;
| [[User:Riley|Riley]]&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Services&amp;diff=23438</id>
		<title>GSP Services</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Services&amp;diff=23438"/>
		<updated>2025-05-01T10:19:00Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
{{Anchor|GSPGPU}}{{Anchor|gsp::Gpu}}&lt;br /&gt;
= GSP service &amp;quot;gsp::Gpu&amp;quot; =&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Command Header&lt;br /&gt;
!  Available since system version&lt;br /&gt;
!  GSP rights required&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00010082&lt;br /&gt;
| &lt;br /&gt;
| Yes&lt;br /&gt;
| [[GSPGPU:WriteHWRegs|WriteHWRegs]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00020084&lt;br /&gt;
| &lt;br /&gt;
| Yes&lt;br /&gt;
| [[GSPGPU:WriteHWRegsWithMask|WriteHWRegsWithMask]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00030082&lt;br /&gt;
| &lt;br /&gt;
| Yes&lt;br /&gt;
| [[GSPGPU:WriteHWRegRepeat|WriteHWRegRepeat]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00040080&lt;br /&gt;
| &lt;br /&gt;
| Yes&lt;br /&gt;
| [[GSPGPU:ReadHWRegs|ReadHWRegs]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00050200&lt;br /&gt;
| &lt;br /&gt;
| Yes&lt;br /&gt;
| [[GSPGPU:SetBufferSwap|SetBufferSwap]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00060082&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| SetCommandList (Stubbed)&lt;br /&gt;
|-&lt;br /&gt;
| 0x000700C2&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| RequestDma (Stubbed)&lt;br /&gt;
|-&lt;br /&gt;
| 0x00080082&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:FlushDataCache|FlushDataCache]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00090082&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:InvalidateDataCache|InvalidateDataCache]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000A0044&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| RegisterInterruptEvents (Stubbed)&lt;br /&gt;
|-&lt;br /&gt;
| 0x000B0040&lt;br /&gt;
| &lt;br /&gt;
| Usually not (see below)&lt;br /&gt;
| [[GSPGPU:SetLcdForceBlack|SetLcdForceBlack]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000C0000&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000D0140&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:SetDisplayTransfer|SetDisplayTransfer]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000E0180&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:SetTextureCopy|SetTextureCopy]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000F0200&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:SetMemoryFill|SetMemoryFill]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00100040&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:SetAxiConfigQoSMode|SetAxiConfigQoSMode]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00110040&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:SetPerfLogMode|SetPerfLogMode]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00120000&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:GetPerfLog|GetPerfLog]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00130042&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00140000&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:UnregisterInterruptRelayQueue|UnregisterInterruptRelayQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00150002&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:TryAcquireRight|TryAcquireRight]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00160042&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:AcquireRight|AcquireRight]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00170000&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:ReleaseRight|ReleaseRight]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00180000&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:ImportDisplayCaptureInfo|ImportDisplayCaptureInfo]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00190000&lt;br /&gt;
| &lt;br /&gt;
| See below&lt;br /&gt;
| [[GSPGPU:SaveVramSysArea|SaveVramSysArea]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x001A0000&lt;br /&gt;
| &lt;br /&gt;
| See below&lt;br /&gt;
| [[GSPGPU:RestoreVramSysArea|RestoreVramSysArea]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x001B0000&lt;br /&gt;
| &lt;br /&gt;
| Yes&lt;br /&gt;
| [[GSPGPU:ResetGpuCore|ResetGpuCore]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x001C0040&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:SetLedForceOff|SetLedForceOff]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x001D0040&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| SetTestCommand (Stubbed)&lt;br /&gt;
|-&lt;br /&gt;
| 0x001E0080&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:SetInternalPriorities|SetInternalPriorities]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x001F0082&lt;br /&gt;
| [[8.0.0-18]]&lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:StoreDataCache|StoreDataCache]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The GSP module starts a thread for handling commands for each service session, a maximum of 4 processes can use this service at once. Official applications have an optional code-path which [[GSPGPU:WriteHWRegs|writes]] to registers during initialization, this is normally not used however.&lt;br /&gt;
&lt;br /&gt;
If a process has forcefully acquired rights (ErrDisp), attempting [[GSPGPU:SetLcdForceBlack|unset LCDs black-fill]] from another process will fail.&lt;br /&gt;
Saving/restoring VRAM requires bit0 of process [[GSPGPU:RegisterInterruptRelayQueue|flags]] to be set.&lt;br /&gt;
&lt;br /&gt;
{{Anchor|GSPLCD}}{{Anchor|gsp::Lcd}}&lt;br /&gt;
= GSP service &amp;quot;gsp::Lcd&amp;quot; =&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Command Header&lt;br /&gt;
!  Available since system version&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00010040&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:EnableABL|EnableABL]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00020040&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:DisableABL|DisableABL]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00030080&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetRSLut|SetRSLut]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000400C0&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetRSParams|SetRSParams]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00050140&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetABLArea|SetABLArea]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00060140&lt;br /&gt;
| &lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00070080&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetInertia|SetInertia]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000800C0&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetDitherMode|SetDitherMode]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00090140&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetDitherParams|SetDitherParams]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000A0080&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetBrightnessRaw|SetBrightnessRaw]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000B0080&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetBrightness|SetBrightness]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000C0040&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:ReloadConfig|ReloadConfig]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000D0040&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:RestoreConfig|RestoreConfig]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000E0000&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:GetPowerState|GetPowerState]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000F0000&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:PowerOnAllBacklights|PowerOnAllBacklights]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00100000&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:PowerOffAllBacklights|PowerOffAllBacklights]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00110040&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:PowerOnBacklight|PowerOnBacklight]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00120040&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:PowerOffBacklight|PowerOffBacklight]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00130040&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetLedForceOff|SetLedForceOff]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00140000&lt;br /&gt;
| [[8.0.0-18]]&lt;br /&gt;
| [[GSPLCD:GetVendor|GetVendor]] New3DS-only, stubbed on Old3DS: This only returns an error. Uninitialized data(not set by this command itself) is also written to u8 cmdreply_word[2].&lt;br /&gt;
|-&lt;br /&gt;
| 0x00150040&lt;br /&gt;
| [[8.0.0-18]]&lt;br /&gt;
| [[GSPLCD:GetBrightness|GetBrightness]] New3DS-only, stubbed on Old3DS: This only returns an error. Uninitialized data(not set by this command itself) is also written to u32 cmdreply_word[2].&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Unlike gsp::Gpu, GSP module does not start a separate thread for handling these service commands.&lt;br /&gt;
&lt;br /&gt;
= Version history =&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Version&lt;br /&gt;
!  Changes&lt;br /&gt;
|-&lt;br /&gt;
| [[8.0.0-18|v8196]]&lt;br /&gt;
| Support for the new LINEAR memory region was implemented for cache commands and vaddr-&amp;gt;paddr conversion. Support for the New3DS only [[Memory_layout#0x1F000000_.28New_3DS_only.29|QTM memory region]] was added for vaddr-&amp;gt;paddr conversion. When vaddr-&amp;gt;physaddr conversions fail while handling PSC commands, an error is now returned (see [[GSP_Shared_Memory#Command_Queue_Header|here]] regarding errors being written to GSP shared memory).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=New3DS=&lt;br /&gt;
There&#039;s separate GSP-module titles for Old3DS and New3DS. PTM CheckNew3DS is only used by the New3DS title, for copying that flag into a state field. Elsewhere that field is checked for running additional code only on New3DS, for processing various state / using [[QTM_Services|QTM]] commands.&lt;br /&gt;
&lt;br /&gt;
[[Category:Services]]&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23437</id>
		<title>GSP Shared Memory</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23437"/>
		<updated>2025-04-23T13:06:26Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the structure of the GSP [[GSPGPU:RegisterInterruptRelayQueue|shared]] memory. Interrupt, framebuffer, and GX command data is stored here.&lt;br /&gt;
&lt;br /&gt;
=Interrupt Queue=&lt;br /&gt;
&lt;br /&gt;
The Interrupt queue is located at sharedMemBase + (clientID * 0x40).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| Offset from the count where to save incoming interrupts&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| Count (max 0x20 for PDC, 0x34 for others)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| Missed other interrupts (set to 1 when 0 and count &amp;gt;= 0x34)&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| Flags (bit0 = skip PDC)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4-0x7&lt;br /&gt;
| Missed PDC0 (incremented when flags.bit0 is clear and count &amp;gt;= 0x20) &lt;br /&gt;
|-&lt;br /&gt;
| 0x8-0xB&lt;br /&gt;
| Missed PDC1 (same as above)&lt;br /&gt;
|-&lt;br /&gt;
| 0xC-0x3F&lt;br /&gt;
| Interrupt list (u8) (0=PSC0, 1=PSC1, 2=PDC0/VBlankTop, 3=PDC1/VBlankBottom, 4=PPF, 5=P3D, 6=DMA)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP fills the interrupt list, then triggers the event set with [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]] for the specified process(es).&lt;br /&gt;
&lt;br /&gt;
PDC interrupts are sent to all processes; other interrupts are only sent to the process with GPU rights.&lt;br /&gt;
&lt;br /&gt;
= Framebuffer Info =&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the top LCD is located at sharedMemBase + 0x200 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the bottom LCD is located at sharedMemBase + 0x240 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
|  Framebuffer info [[GSPGPU:SetBufferSwap|entry]] index&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Flags (bit0 = client has set new data)&lt;br /&gt;
|-&lt;br /&gt;
| 3-2&lt;br /&gt;
| Padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Structure ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Active framebuffer (0 = first, 1 = second)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Left framebuffer VA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Right framebuffer VA (top screen only)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Stride]] (offset 0x90)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| [[GPU/External_Registers#Framebuffer_format|Format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Status]] (offset 0x78)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ? (&amp;quot;Attribute&amp;quot;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a process sets this framebuffer info, it sets index to &amp;lt;nowiki&amp;gt;(index+1) &amp;amp; 1&amp;lt;/nowiki&amp;gt;. Then it writes the framebuffer info entry, and sets flag to value 1. The GSP module loads this framebuffer info entry data into GSP state once the [[GPU]] finishes processing GX commands 3 or 4. Once the GSP module finishes loading this framebuffer info, it sets flag to value 0, then it will not load the framebuffer info again until flag is value 1. After loading this entry data into GSP state, the GSP module then writes this framebuffer state to the [[LCD]] registers. GSP module automatically updates the LCD framebuffer registers each time GX commands 3 or 4 finish, even when this shared memory data was not updated by the application.(GSP module toggles the active framebuffer register when automatically updating LCD registers, when shared memory data is not used)&lt;br /&gt;
&lt;br /&gt;
The two 0x1C-byte framebuffer info entries are located at framebufferinfo+4.&lt;br /&gt;
&lt;br /&gt;
= 3D Slider and 3D [[GSPGPU:SetLedForceOff|LED]] =&lt;br /&gt;
&lt;br /&gt;
See [[Configuration Memory]].&lt;br /&gt;
&lt;br /&gt;
= Command Queue =&lt;br /&gt;
&lt;br /&gt;
The command queue is located at sharedMemBase + 0x800 + (clientID * 0x200). It consists of an header followed by at most 15 command entries. Each command entry is of size 0x20 and has an header followed by command specific parameters.&lt;br /&gt;
&lt;br /&gt;
After adding a command, [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]] must be used to start command processing (official code does so when the total commands field is 1).&lt;br /&gt;
&lt;br /&gt;
== Command Queue Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Index of the command to process, this is incremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Total commands to process, this is incremented by the application when adding the command to the queue, and decremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Status (bit0 = halted, bit7 = fatal error)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When bit0 is set, further processing of commands is halted until the client resets the flag and calls [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 7-4&lt;br /&gt;
| Result code for the last command which failed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP checks for status.bit0 and optionally avoids handling further commands, however the check is done by equality, which means it will always fail if status.bit7 is also set (and thus other commands will be processed). This bug prevents the halting logic from working propertly, but can be worked around by keeping bit0 of word3 set, as that will force halting on each iteration.&lt;br /&gt;
&lt;br /&gt;
== Command Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| When bit0 is set, GSP stops processing further commands (can be used for packing together sets of commands)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When set, the command fails if GSP is busy handling any other command; otherwise, it only fails if GSP is busy handling a command of the same kind&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Commands ==&lt;br /&gt;
&lt;br /&gt;
Addresses specified in parameters are virtual addresses. For applications these are normally located in GSP memory, while for other processes they are located in VRAM.&lt;br /&gt;
&lt;br /&gt;
Address and size parameters except for command 0 and command 5 must be 8-byte aligned.&lt;br /&gt;
&lt;br /&gt;
=== Trigger DMA Request ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x00)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush source (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command is normally used to DMA data from the application GSP [[Memory_layout|heap]] to VRAM. When flushing is enabled and the source buffer is not located within VRAM, svcFlushProcessDataCache is used to flush the source buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Command List Processing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x01)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Update gas additive blend results (0 = don&#039;t update, 1 = update)&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush buffer (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified address to a physical address, then writes the physical address and size to the [[GPU]] registers at 0x1EF018E0. This buffer contains [[GPU/Internal_Registers|GPU commands]]. When flushing is enabled, svcFlushProcessDataCache is used to flush the buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Memory Fill ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x02)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 start address (0 = don&#039;t fill anything)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 value&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf0 end address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 start address (0 = don&#039;t fill anything)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf1 value&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf1 end address&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Control0 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (Control1 &amp;lt;&amp;lt; 16)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these addresses and the specified parameters to the [[GPU]] registers at 0x1EF00010 and 0x1EF00020. Doing so fills the specified buffers with the associated 4-byte value. This is used to clear GPU framebuffers.&lt;br /&gt;
The associated buffer address must not be &amp;lt;= to the main buffer address, thus the associated buffer address must not be zero as well. When the bufX address is zero, processing for the bufX parameters is skipped.&lt;br /&gt;
&lt;br /&gt;
The values of Control0 and Control1 give information about the type of memory fill. See [[GPU/External_Registers#Memory Fill|here]] for more information about memory fill parameters.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Display Transfer ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x03)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Input framebuffer [[GPU|dimensions]]&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Output framebuffer dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU|Flags]], for applications this is 0x1001000 for the main screen, and 0x1000 for the sub screen.&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these physical addresses and parameters to the [[GPU]] registers at 0x1EF00C00. This GPU command copies the already rendered framebuffer data from the input GPU framebuffer address to the specified output LCD framebuffer. The input framebuffer is normally located in VRAM.&lt;br /&gt;
&lt;br /&gt;
The GPU color buffer is stored in the same Z-curve (tiled) format as textures. By default, SetDisplayTransfer converts the given buffer from the tiled format to a linear format adapted to the LCD framebuffers.&lt;br /&gt;
&lt;br /&gt;
Display transfers are performed asynchronously, so after requesting a display transfer you should wait for the PPF interrupt to fire before reading the output data.&lt;br /&gt;
&lt;br /&gt;
The minimum supported dimension for output is 64x64, anything lower will hang the engine.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Texture Copy ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x04)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Total bytes to copy, not including gaps.&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Bits 0-15: Size of input line, in bytes. Bits 16-31: Gap between input lines, in bytes.&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Same as 4, but for the output.&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Flags, corresponding to the [[GPU/External_Registers#Transfer_Engine|Transfer Engine flags]]. However, for TextureCopy commands, bit 3 is always set, bit 2 is set if any output dimension is smaller than the input, and other bits are always 0.&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command is similar to cmd3. It also triggers the [[GPU/External_Registers#Transfer_Engine|GPU Transfer Engine]], but setting the TextureCopy parameters.&lt;br /&gt;
&lt;br /&gt;
=== Flush Cache Regions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x05)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf1 address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 size&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf2 address&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf2 size&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The application buffer addresses specified in the parameters are used with [[SVC|svcFlushProcessDataCache]]. The input buf0 size must not be zero. When buf1 size is zero, svcFlushProcessDataCache() for buf1 and buf2 are skipped. When buf2 size is zero, svcFlushProcessDataCache() for buf2 is skipped.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23436</id>
		<title>GSP Shared Memory</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23436"/>
		<updated>2025-04-23T13:04:46Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Document halting bug&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the structure of the GSP [[GSPGPU:RegisterInterruptRelayQueue|shared]] memory. Interrupt, framebuffer, and GX command data is stored here.&lt;br /&gt;
&lt;br /&gt;
=Interrupt Queue=&lt;br /&gt;
&lt;br /&gt;
The Interrupt queue is located at sharedMemBase + (clientID * 0x40).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| Offset from the count where to save incoming interrupts&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| Count (max 0x20 for PDC, 0x34 for others)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| Missed other interrupts (set to 1 when 0 and count &amp;gt;= 0x34)&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| Flags (bit0 = skip PDC)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4-0x7&lt;br /&gt;
| Missed PDC0 (incremented when flags.bit0 is clear and count &amp;gt;= 0x20) &lt;br /&gt;
|-&lt;br /&gt;
| 0x8-0xB&lt;br /&gt;
| Missed PDC1 (same as above)&lt;br /&gt;
|-&lt;br /&gt;
| 0xC-0x3F&lt;br /&gt;
| Interrupt list (u8) (0=PSC0, 1=PSC1, 2=PDC0/VBlankTop, 3=PDC1/VBlankBottom, 4=PPF, 5=P3D, 6=DMA)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP fills the interrupt list, then triggers the event set with [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]] for the specified process(es).&lt;br /&gt;
&lt;br /&gt;
PDC interrupts are sent to all processes; other interrupts are only sent to the process with GPU rights.&lt;br /&gt;
&lt;br /&gt;
= Framebuffer Info =&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the top LCD is located at sharedMemBase + 0x200 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the bottom LCD is located at sharedMemBase + 0x240 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
|  Framebuffer info [[GSPGPU:SetBufferSwap|entry]] index&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Flags (bit0 = client has set new data)&lt;br /&gt;
|-&lt;br /&gt;
| 3-2&lt;br /&gt;
| Padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Structure ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Active framebuffer (0 = first, 1 = second)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Left framebuffer VA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Right framebuffer VA (top screen only)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Stride]] (offset 0x90)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| [[GPU/External_Registers#Framebuffer_format|Format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Status]] (offset 0x78)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ? (&amp;quot;Attribute&amp;quot;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a process sets this framebuffer info, it sets index to &amp;lt;nowiki&amp;gt;(index+1) &amp;amp; 1&amp;lt;/nowiki&amp;gt;. Then it writes the framebuffer info entry, and sets flag to value 1. The GSP module loads this framebuffer info entry data into GSP state once the [[GPU]] finishes processing GX commands 3 or 4. Once the GSP module finishes loading this framebuffer info, it sets flag to value 0, then it will not load the framebuffer info again until flag is value 1. After loading this entry data into GSP state, the GSP module then writes this framebuffer state to the [[LCD]] registers. GSP module automatically updates the LCD framebuffer registers each time GX commands 3 or 4 finish, even when this shared memory data was not updated by the application.(GSP module toggles the active framebuffer register when automatically updating LCD registers, when shared memory data is not used)&lt;br /&gt;
&lt;br /&gt;
The two 0x1C-byte framebuffer info entries are located at framebufferinfo+4.&lt;br /&gt;
&lt;br /&gt;
= 3D Slider and 3D [[GSPGPU:SetLedForceOff|LED]] =&lt;br /&gt;
&lt;br /&gt;
See [[Configuration Memory]].&lt;br /&gt;
&lt;br /&gt;
= Command Queue =&lt;br /&gt;
&lt;br /&gt;
The command queue is located at sharedMemBase + 0x800 + (clientID * 0x200). It consists of an header followed by at most 15 command entries. Each command entry is of size 0x20 and has an header followed by command specific parameters.&lt;br /&gt;
&lt;br /&gt;
After adding a command, [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]] must be used to start command processing (official code does so when the total commands field is 1).&lt;br /&gt;
&lt;br /&gt;
== Command Queue Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Index of the command to process, this is incremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Total commands to process, this is incremented by the application when adding the command to the queue, and decremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Status (bit0 = halted, bit7 = fatal error)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When bit0 is set, further processing of commands is halted until the client resets the flag and calls [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 7-4&lt;br /&gt;
| Result code for the last command which failed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP checks for status.bit0 and optionally avoids handling further commands, however the check is done by equality, which means it will always fail if status.bit7 is also set (and thus other commands will be processed). This bug prevents the halting logic from working propertly, but can worked around by keeping bit0 of word3 set, as that will force halting on each iteration.&lt;br /&gt;
&lt;br /&gt;
== Command Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| When bit0 is set, GSP stops processing further commands (can be used for packing together sets of commands)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When set, the command fails if GSP is busy handling any other command; otherwise, it only fails if GSP is busy handling a command of the same kind&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Commands ==&lt;br /&gt;
&lt;br /&gt;
Addresses specified in parameters are virtual addresses. For applications these are normally located in GSP memory, while for other processes they are located in VRAM.&lt;br /&gt;
&lt;br /&gt;
Address and size parameters except for command 0 and command 5 must be 8-byte aligned.&lt;br /&gt;
&lt;br /&gt;
=== Trigger DMA Request ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x00)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush source (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command is normally used to DMA data from the application GSP [[Memory_layout|heap]] to VRAM. When flushing is enabled and the source buffer is not located within VRAM, svcFlushProcessDataCache is used to flush the source buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Command List Processing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x01)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Update gas additive blend results (0 = don&#039;t update, 1 = update)&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush buffer (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified address to a physical address, then writes the physical address and size to the [[GPU]] registers at 0x1EF018E0. This buffer contains [[GPU/Internal_Registers|GPU commands]]. When flushing is enabled, svcFlushProcessDataCache is used to flush the buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Memory Fill ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x02)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 start address (0 = don&#039;t fill anything)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 value&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf0 end address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 start address (0 = don&#039;t fill anything)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf1 value&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf1 end address&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Control0 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (Control1 &amp;lt;&amp;lt; 16)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these addresses and the specified parameters to the [[GPU]] registers at 0x1EF00010 and 0x1EF00020. Doing so fills the specified buffers with the associated 4-byte value. This is used to clear GPU framebuffers.&lt;br /&gt;
The associated buffer address must not be &amp;lt;= to the main buffer address, thus the associated buffer address must not be zero as well. When the bufX address is zero, processing for the bufX parameters is skipped.&lt;br /&gt;
&lt;br /&gt;
The values of Control0 and Control1 give information about the type of memory fill. See [[GPU/External_Registers#Memory Fill|here]] for more information about memory fill parameters.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Display Transfer ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x03)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Input framebuffer [[GPU|dimensions]]&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Output framebuffer dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU|Flags]], for applications this is 0x1001000 for the main screen, and 0x1000 for the sub screen.&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these physical addresses and parameters to the [[GPU]] registers at 0x1EF00C00. This GPU command copies the already rendered framebuffer data from the input GPU framebuffer address to the specified output LCD framebuffer. The input framebuffer is normally located in VRAM.&lt;br /&gt;
&lt;br /&gt;
The GPU color buffer is stored in the same Z-curve (tiled) format as textures. By default, SetDisplayTransfer converts the given buffer from the tiled format to a linear format adapted to the LCD framebuffers.&lt;br /&gt;
&lt;br /&gt;
Display transfers are performed asynchronously, so after requesting a display transfer you should wait for the PPF interrupt to fire before reading the output data.&lt;br /&gt;
&lt;br /&gt;
The minimum supported dimension for output is 64x64, anything lower will hang the engine.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Texture Copy ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x04)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Total bytes to copy, not including gaps.&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Bits 0-15: Size of input line, in bytes. Bits 16-31: Gap between input lines, in bytes.&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Same as 4, but for the output.&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Flags, corresponding to the [[GPU/External_Registers#Transfer_Engine|Transfer Engine flags]]. However, for TextureCopy commands, bit 3 is always set, bit 2 is set if any output dimension is smaller than the input, and other bits are always 0.&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command is similar to cmd3. It also triggers the [[GPU/External_Registers#Transfer_Engine|GPU Transfer Engine]], but setting the TextureCopy parameters.&lt;br /&gt;
&lt;br /&gt;
=== Flush Cache Regions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x05)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf1 address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 size&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf2 address&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf2 size&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The application buffer addresses specified in the parameters are used with [[SVC|svcFlushProcessDataCache]]. The input buf0 size must not be zero. When buf1 size is zero, svcFlushProcessDataCache() for buf1 and buf2 are skipped. When buf2 size is zero, svcFlushProcessDataCache() for buf2 is skipped.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23425</id>
		<title>GPU/External Registers</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=23425"/>
		<updated>2025-04-13T09:35:42Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Miss Information&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the address range accessible from the ARM11, used to configure the basic GPU functionality. For information about the internal registers used for 3D rendering, see [[GPU/Internal Registers]].&lt;br /&gt;
&lt;br /&gt;
== Map ==&lt;br /&gt;
Address mappings for the external registers. GSPGPU:WriteHWRegs takes these addresses relative to 0x1EB00000. &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! User VA&lt;br /&gt;
! PA&lt;br /&gt;
! Length&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00000&lt;br /&gt;
| 0x10400000&lt;br /&gt;
| 4&lt;br /&gt;
| Hardware ID&lt;br /&gt;
| Bit2: new model&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00004&lt;br /&gt;
| 0x10400004&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00010&lt;br /&gt;
| 0x10400010&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill1]] &amp;quot;PSC0&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00020&lt;br /&gt;
| 0x10400020&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill2]] &amp;quot;PSC1&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00030&lt;br /&gt;
| 0x10400030&lt;br /&gt;
| 4&lt;br /&gt;
| VRAM bank control&lt;br /&gt;
| Bits 8-11 = bank[i] disabled; other bits are unused.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00034&lt;br /&gt;
| 0x10400034&lt;br /&gt;
| 4&lt;br /&gt;
| GPU Busy&lt;br /&gt;
| Bit26 = PSC0, bit27 = PSC1, Bit30 = PPF, Bit31 = P3D&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00050&lt;br /&gt;
| 0x10400050&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x22221200 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00054&lt;br /&gt;
| 0x10400054&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFF2 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000C0&lt;br /&gt;
| 0x104000C0&lt;br /&gt;
| 4&lt;br /&gt;
| Backlight control&lt;br /&gt;
| Writes 0x0 to allow backlights to turn off, 0x20000000 to force them always on.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00400&lt;br /&gt;
| 0x10400400&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC0&amp;quot; (top screen)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00500&lt;br /&gt;
| 0x10400500&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC1&amp;quot; (bottom)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| 0x10400C00&lt;br /&gt;
| ?&lt;br /&gt;
| [[#Transfer_Engine|Transfer Engine]] &amp;quot;DMA&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|colspan=&amp;quot;5&amp;quot;| 0x1EF01000/0x10401000 - 0x1EF01C00/0x10401C00 maps to [[GPU/Internal_Registers|GPU internal registers]]. These registers are usually not read/written directly here, but are written using the command list interface below (corresponding to the GPUREG_CMDBUF_* internal registers)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01000&lt;br /&gt;
| 0x10401000&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0 on GPU init and before the Command List is used&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01080&lt;br /&gt;
| 0x10401080&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x12345678 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010C0&lt;br /&gt;
| 0x104010C0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFFFFFFF0 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010D0&lt;br /&gt;
| 0x104010D0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 1 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF014??&lt;br /&gt;
| 0x104014??&lt;br /&gt;
| 0x14&lt;br /&gt;
| &amp;quot;PPF&amp;quot; ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| 0x104018E0&lt;br /&gt;
| 0x14&lt;br /&gt;
| [[#Command_List|Command List]] &amp;quot;P3D&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Memory Fill ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  User VA&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X0&lt;br /&gt;
| Buffer start physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X4&lt;br /&gt;
| Buffer end physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X8&lt;br /&gt;
| Fill value&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000XC&lt;br /&gt;
| Control. bit0: start/busy, bit1: finished, bit8-9: fill-width (0=16bit, 1=3=24bit, 2=32bit)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Memory fills are used to initialize buffers in memory with a given value, similar to memset. A memory fill is triggered by setting bit0 in the control register. Doing so aborts any running memory fills on that filling unit. Upon completion, the hardware unsets bit0 and sets bit1 and fires interrupt PSC0.&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP Shared Memory#GX SetMemoryFill|GX SetMemoryFill]].&lt;br /&gt;
&lt;br /&gt;
== LCD Source Framebuffer Setup ==&lt;br /&gt;
&lt;br /&gt;
All of these registers must be accessed with 32bit operations regardless of the registers&#039; actual bit size.&lt;br /&gt;
&lt;br /&gt;
The naming of these parameters reflects the physical characteristics of the displays, and not the way the 3DS is normally held.&lt;br /&gt;
&lt;br /&gt;
To make sense of these values, the 3DS must be held in a way, so that the bottom screen is in the left hand, and the top screen is in the right hand, and that way the first pixel will be in the top-left corner, as it should be. If the 3DS is held normally, the first pixel is in the bottom-left corner.&lt;br /&gt;
&lt;br /&gt;
All pixel and scanline timing values are 12bits, unless noted. This also applies to those fields where two u16 are combined into one register. Each u16 field is only 12bits in size. timin&lt;br /&gt;
&lt;br /&gt;
The horizontal timing parameter order is as follows (values may overflow through HTotal register value):&lt;br /&gt;
 0x10 &amp;lt; 0x14 &amp;lt;= 0x60.LO &amp;lt;= 0x04 &amp;lt;= 0x60.HI &amp;lt;= 0x08 &amp;lt;= 0x0C &amp;lt;= 0x10&lt;br /&gt;
 0x18 &amp;lt;= 0x60.LO&lt;br /&gt;
&lt;br /&gt;
Timing starts from HCount == 0, then each absolute value in the beforementioned register chain triggers when HCount == register, latching the primitive display controller into a new mode.&lt;br /&gt;
There is an inherent latch order, where if two simultenaous events occur, one event wins over another.&lt;br /&gt;
&lt;br /&gt;
 Known latched modes (in order):&lt;br /&gt;
 - HSync (triggers a line to the LCD to move to the next line)&lt;br /&gt;
 - Back porch (area between HSync and border being displayed, no pixels pushed, min 16 pixel clocks, otherwise the screen gets glitchy)&lt;br /&gt;
 - Left border start (no image data is being displayed, just a configurable solid color)&lt;br /&gt;
 - Image start (pixel data is being DMA&#039;d from video memory or main RAM)&lt;br /&gt;
 - Right border start/Image end (border color is being displayed after the main image)&lt;br /&gt;
 - Unknown synchronization (supposed to be probably right border end, but this mode seems to be broken or not do anything)&lt;br /&gt;
 - Front porch (no pixels pushed, 68 clock min, otherwise the screen doesn&#039;t sync properly, and really glitches out)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Offset&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| HTotal&lt;br /&gt;
| The total width of a timing scanline. In other words, this is the horizontal refresh clock divider value.&lt;br /&gt;
&lt;br /&gt;
HClock = PClock / (HTotal + 1)&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| HStart&lt;br /&gt;
| Determines when the image is going to be displayed in the visible region (register 0x60).&lt;br /&gt;
|-&lt;br /&gt;
| 0x08&lt;br /&gt;
| HBR&lt;br /&gt;
| Right border start(?). Does nothing.&lt;br /&gt;
&lt;br /&gt;
While this register seems to have no impact on the image whatsoever, it still has to be set to a valid value.&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| 0x0C&lt;br /&gt;
| HPF&lt;br /&gt;
| Front porch. The image is blanked during this period, and no pixels are pushed to the LCD.&lt;br /&gt;
&lt;br /&gt;
Unknown why, but a single dot of red is displayed before entering this mode.&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| HSync&lt;br /&gt;
| Triggers a HSync pulse.&lt;br /&gt;
&lt;br /&gt;
Based on behavior, this needs to last at least a pixel clock for the LCD to register the sync.&lt;br /&gt;
|-&lt;br /&gt;
| 0x14&lt;br /&gt;
| HPB&lt;br /&gt;
| Back porch? Has to be at least one bigger than HSync, otherwise HSync never triggers.&lt;br /&gt;
&lt;br /&gt;
The display is blank, and the LCD displays nothing in this period (doesn&#039;t push pixels).&lt;br /&gt;
|-&lt;br /&gt;
| 0x18&lt;br /&gt;
| HBL&lt;br /&gt;
| Left border trigger treshold. Enables pushing pixels to the display.&lt;br /&gt;
&lt;br /&gt;
If this value is smaller than the back porch, then the back porch period will be zero, and the border will be immediately displayed upon entering the back porch period.&lt;br /&gt;
&lt;br /&gt;
Can be lower than HSync, as the back porch is what takes the controller out of HSync.&lt;br /&gt;
&lt;br /&gt;
Must be &amp;lt;= HDisp start (reg 0x60 low u16), otherwise no pixels will be pushed due to a glitched state.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1C&lt;br /&gt;
| H Interrupt timing&lt;br /&gt;
| Made up from two u16 values, PDC interrupt line is asserted when HCount == low u16, and most likely deasserted when HCount == high u16.&lt;br /&gt;
&lt;br /&gt;
There seems to be some limitations though:&lt;br /&gt;
* low u16 must be smaller than high u16&lt;br /&gt;
* if low u16 is less than HTotal then high u16 must also be smaller than HTotal&lt;br /&gt;
* setting low u16 to &amp;gt;= HTotal disables the interrupt ever firing&lt;br /&gt;
&lt;br /&gt;
This is configured by gsp in a way so that low u16 equals to HTotal, meaning the HSync interrupt will never fire.&lt;br /&gt;
|-&lt;br /&gt;
| 0x20&lt;br /&gt;
| low u16: ???&lt;br /&gt;
high u16: ???&lt;br /&gt;
| ???&lt;br /&gt;
|-&lt;br /&gt;
| 0x24&lt;br /&gt;
| VTotal&lt;br /&gt;
| Total height of the timing window. Can be interpreted as the vertical clock divider.&lt;br /&gt;
&lt;br /&gt;
VClock = PClock / (HTotal + 1) / (VTotal + 1)&lt;br /&gt;
&lt;br /&gt;
Setting this to 494 lowers framerate to about 50.040660858 Hz ((268111856 / 24) / (450 + 1) / (494 + 1)).&lt;br /&gt;
|-&lt;br /&gt;
| 0x28&lt;br /&gt;
| ?&lt;br /&gt;
| Seems to determine the vertical blanking interval.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Setting this to lower than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will cut off the top &amp;lt;code&amp;gt;VTotal - VDisp - thisvalue&amp;lt;/code&amp;gt; lines.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will make the image be pushed downwards with the overscan color visible.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;HTotal&amp;lt;/code&amp;gt; will make the GPU skip vertical pixel data synchronization (hence filling the screen with the rest of the pixel data past the given screen framebuffer size). Also will skip &amp;lt;code&amp;gt;thisvalue + somevalue - HTotal&amp;lt;/code&amp;gt; lines into the &amp;quot;global&amp;quot; pixel buffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x30&lt;br /&gt;
| ?&lt;br /&gt;
| Total amount of vertical scanlines in the pixel buffer, must be bigger than *an unknown blanking-like value*. If this value is less than VDisp then the last two scanlines will be repeated interlaced until VDisp is reached.&lt;br /&gt;
|-&lt;br /&gt;
| 0x34&lt;br /&gt;
| VDisp(?)&lt;br /&gt;
| Total amonut of vertical scanlines displayed (only for top screen it seems like). If this value is less than VTotal then the rest of the scanlines will not be updated on the screen, so those will slowly fade out. Must be bigger than *an unknown blanking-like value*, otherwise an underflow will happen.&lt;br /&gt;
|-&lt;br /&gt;
| 0x38&lt;br /&gt;
| Vertical data offset(?)&lt;br /&gt;
| ??? Seems to offset the screen upwards if this value is high enough. If this value is higher or equal to *some value* (aka. if less than one scanline is displayed on the screen) then the screen will lose synchronization.&lt;br /&gt;
|-&lt;br /&gt;
| 0x40&lt;br /&gt;
| V Interrupt timing&lt;br /&gt;
| Similar to H Interrupt timing (0x1C), except the comparison is done against VCount, the limitations are emposed on VTotal, and the interrupt that fires is VSync.&lt;br /&gt;
&lt;br /&gt;
One important note is that it seems like the VSync interrupt always fires at HCount == 0, and there doesn&#039;t seem to be a register to control this behavior.&lt;br /&gt;
|-&lt;br /&gt;
| 0x44&lt;br /&gt;
| ???&lt;br /&gt;
| similar functionality to 0x10&lt;br /&gt;
|-&lt;br /&gt;
| 0x48&lt;br /&gt;
| ???&lt;br /&gt;
| bit0 seems to disable HSync, bit8 seems to disable VSync, rest of the bits aren&#039;t writable.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4C&lt;br /&gt;
| Overscan filler color&lt;br /&gt;
| 24bits(? top 8bits ignored)&lt;br /&gt;
&lt;br /&gt;
When the visible region is being drawn, but the timing parameters are set up in a way that the framebuffer is smaller than the visible region, it will be filled by this color.&lt;br /&gt;
|-&lt;br /&gt;
| 0x50&lt;br /&gt;
| HCount&lt;br /&gt;
| Horizontal &amp;quot;beam position&amp;quot; counter. Note that this value does not equal to the current pixel being drawn.&lt;br /&gt;
|-&lt;br /&gt;
| 0x54&lt;br /&gt;
| VCount&lt;br /&gt;
| Vertical &amp;quot;beam position&amp;quot; counter. Note that the scanline being drawn isn&#039;t equal to this value.&lt;br /&gt;
|-&lt;br /&gt;
| 0x5C&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: Image width (including some offset?)&lt;br /&gt;
high u16: Image height??? (seems to be unused)&lt;br /&gt;
|-&lt;br /&gt;
| 0x60&lt;br /&gt;
| HDisp&lt;br /&gt;
| low u16: Image start (border --&amp;gt; pixel data)&lt;br /&gt;
high u16: Image end (pixel data --&amp;gt; border)&lt;br /&gt;
|-&lt;br /&gt;
| 0x64&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: unknown&lt;br /&gt;
high u16: framebuffer total height (amount of scanlines blitted regardless of framebuffer height)&lt;br /&gt;
|-&lt;br /&gt;
| 0x68&lt;br /&gt;
| Framebuffer A first address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x6C&lt;br /&gt;
| Framebuffer A second address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x70&lt;br /&gt;
| Framebuffer format and other settings&lt;br /&gt;
| See [[#Framebuffer_format|framebuffer format]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x74&lt;br /&gt;
| PDC control&lt;br /&gt;
| Bit 0: Enable display controller.&lt;br /&gt;
Bit 8: HBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 9: VBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 10: Error IRQ mask? (0 = enabled).&lt;br /&gt;
Bit 16: Output enable?&lt;br /&gt;
|-&lt;br /&gt;
| 0x78&lt;br /&gt;
| Framebuffer select and status&lt;br /&gt;
| Bit 0: Next framebuffer to display (after VBlank).&lt;br /&gt;
Bit 4: Currently displaying framebuffer?&lt;br /&gt;
Bit 8: Reset FIFO?&lt;br /&gt;
Bit 16: HBlank IRQ status/ack. Write 1 to aknowledge.&lt;br /&gt;
Bit 17: VBlank IRQ status/ack.&lt;br /&gt;
Bit 18: Error IRQ status/ack?&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| Color lookup table index select&lt;br /&gt;
| 8bits, write-only&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| Color lookup table indexed element&lt;br /&gt;
| Contains the value of the color lookup table indexed by the above register, 24bits, RGB8 (0x00BBGGRR)  &lt;br /&gt;
Accessing this register will increase the index register by one&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| Framebuffer stride&lt;br /&gt;
| 32bits (bottom 3bits ignored?)&lt;br /&gt;
&lt;br /&gt;
Distance in bytes between the start of two framebuffer rows (must be a multiple of 8).&lt;br /&gt;
&lt;br /&gt;
In other words, this can be interpreted as the amount to add to the framebuffer pointer after displaying a scanline.&lt;br /&gt;
&lt;br /&gt;
Setting this to zero will cause only the first line of the image to be displayed repeated on the entire display. With the HSync interrupt it&#039;s possible to &amp;quot;race the beam&amp;quot; to (ab)use this feature.&lt;br /&gt;
&lt;br /&gt;
Because of this simplicity, writing a negative value here VFlips the image, although that requires the framebuffer pointer register to be set to the start of the last scanline, instead of at the start of the framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| Framebuffer B first address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| Framebuffer B second address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer format ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 2-0&lt;br /&gt;
| [[#Framebuffer_color_formats|Color format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5-4&lt;br /&gt;
| Framebuffer interlacing mode&lt;br /&gt;
&lt;br /&gt;
 0 - A  (no interlacing)&lt;br /&gt;
 1 - AA (scanline doubling)&lt;br /&gt;
 2 - AB (interlace enable)&lt;br /&gt;
 3 - BA (same as above, but the fields are inverted)&lt;br /&gt;
&lt;br /&gt;
In AB and BA interlace modes, a scanline from each framebuffer is output in an alternating manner. In AB mode, Framebuffer A is output on the frist display scanline. Similarly, in BA mode, Framebuffer B gets output to the first display scanline.&lt;br /&gt;
&lt;br /&gt;
The way AB and BA modes work, is that a scanline is output, the framebuffer stride value is added to the internal scanline pointer value, and the other framebuffer is selected. And this alternates until the end of the draw region.&lt;br /&gt;
&lt;br /&gt;
AA interlacing works like AB interlacing, except both internal framebuffer pointers are set to the Framebuffer A pointer value.&lt;br /&gt;
&lt;br /&gt;
In A mode (no interlacing), it doesn&#039;t switch to the other framebuffer at the end of outpuitting a scanline to the display.&lt;br /&gt;
&lt;br /&gt;
Bottom screen has this set to 0 (A mode, no interlacing) at all times.  &lt;br /&gt;
Top screen uses AB interlacing in 3D mode (with 3D slider enabled), and A mode (no interlacing) in 2D mode.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Alternative pixel output mode*&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 9-8&lt;br /&gt;
| DMA size&lt;br /&gt;
&lt;br /&gt;
 0 -  4 FCRAM words (32 bytes)&lt;br /&gt;
 1 -  8 FCRAM words (64 bytes)&lt;br /&gt;
 2 - 16 FCRAM words (128 bytes)&lt;br /&gt;
 3 - ???&lt;br /&gt;
&lt;br /&gt;
FCRAM doesn&#039;t support DMA size 3, as it can only burst up to 16 words (128 bytes), and will show a black screen instead.&lt;br /&gt;
|-&lt;br /&gt;
| 31-16&lt;br /&gt;
| Unknown&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt; The weird thing about bit6, is that it works different between the bottom and top LCD. On the bottom LCD, it doubles the number of outputted pixels (so the same pixel is outputted twice, effectively doing pixel/column doubling). However on the top screen, it does scanline doubling instead.  &lt;br /&gt;
Most likely the top screen receives two pixels at once per clock unit, outputting two scanlines simultaneously.&lt;br /&gt;
&lt;br /&gt;
On a 2DS, it seems to have no effect on the top part of the display, and on the bottom screen it just shifts the framebuffer to the right two pixels.&lt;br /&gt;
&lt;br /&gt;
GSP module only allows the LCD stereoscopy (3D) to be enabled when bit5=1 and bit6=0 here. When GSP module updates this register, GSP module will automatically disable the stereoscopy if those bits are not set for enabling stereoscopy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When both interlacing and alternative mode is disabled (bit6=0), the full resolution of the top screen (240x800) can be utilized if the PDC registers are updated to accomodate this higher resolution. GSP contains tables for this mode (gsp mode == 1). GSP automatically applies this mode if both bit5 and bit6 are cleared. This is also the default, and the only valid mode for the bottom screen in userland.&lt;br /&gt;
&lt;br /&gt;
If only AB interlacing is enabled (bit5=1, bit6=0), gsp detects this as a request to switch to 3D mode (gsp mode == 2), and enables the parallax barrier.  &lt;br /&gt;
It&#039;s unknown how to control this, but some other PDC registers control if interlacing should be done by true interleaving (both framebuffers are treated as 240x400), or by skipping lines (both framebuffers are treated as 240x800).&lt;br /&gt;
&lt;br /&gt;
If only alternative mode is enabled (bit5=0, bit6=1), gsp detects it as a request to switch back to 2D mode for the top screen (gsp mode == 0). This is also the default mode for the top screen.&lt;br /&gt;
&lt;br /&gt;
Both interlacing and scan doubling can&#039;t be enabled in usermode, but it works as expected in baremetal.&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer color formats ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Value&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| GL_RGBA8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| GL_RGB8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| GL_RGB565_OES&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| GL_RGB5_A1_OES&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| GL_RGBA4_OES&lt;br /&gt;
|}&lt;br /&gt;
Color components are laid out in reverse byte order, with the most significant bits used first (i.e. non-24-bit pixels are stored as a little-endian values). For instance, a raw data stream of two GL_RGB565_OES pixels looks like GGGBBBBB RRRRRGGG GGGBBBBB RRRRRGGG.&lt;br /&gt;
&lt;br /&gt;
Color formats 5, 6, and 7 are blocked by gsp, but they behave as pixel-doubled RGBA8 (not line doubling, but instead the same pixel is output twice) if used outside of userland.&lt;br /&gt;
&lt;br /&gt;
== Transfer Engine ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| Input physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C04&lt;br /&gt;
| Output physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C08&lt;br /&gt;
| DisplayTransfer output width (bits 0-15) and height (bits 16-31).&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C0C&lt;br /&gt;
| DisplayTransfer input width and height.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C10&lt;br /&gt;
| Transfer flags. (See below)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C14&lt;br /&gt;
| GSP module writes value 0 here prior to writing to 0x1EF00C18, for cmd3.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C18&lt;br /&gt;
|  Setting bit0 starts the transfer. Upon completion, bit0 is unset and bit8 is set.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C1C&lt;br /&gt;
|  ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C20&lt;br /&gt;
| TextureCopy total amount of data to copy, in bytes.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C24&lt;br /&gt;
| TextureCopy input line width (bits 0-15) and gap (bits 16-31), in 16 byte units.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C28&lt;br /&gt;
| TextureCopy output line width and gap.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP_Shared_Memory|GX command]] 3 and 4. For cmd4, *0x1EF00C18 |= 1 is used instead of just writing value 1. The DisplayTransfer registers are only used if bit 3 of the flags is unset and ignored otherwise. The TextureCopy registers are likewise only used if bit 3 is set, and ignored otherwise.&lt;br /&gt;
&lt;br /&gt;
==== Flags Register - 0x1EF00C10 ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| When set, the framebuffer data is flipped vertically.&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| When set, the input framebuffer is treated as linear and converted to tiled in the output, converts tiled-&amp;gt;linear when unset.&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| This bit is required when the output width is less than the input width for the hardware to properly crop the lines, otherwise the output will be mis-aligned.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Uses a TextureCopy mode transfer. See below for details.&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Don&#039;t perform tiled-linear conversion. Incompatible with bit 1, so only tiled-tiled transfers can be done, not linear-linear.&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 10-8&lt;br /&gt;
| Input framebuffer color format, value0 and value1 are the same as the [[GPU Registers#Framebuffer_color_formats|LCD Source Framebuffer Formats]] (usually zero)&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 14-12&lt;br /&gt;
| Output framebuffer color format&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| Use 32x32 block tiling mode, instead of the usual 8x8 one. Output dimensions must be multiples of 32, even if cropping with bit 2 set above.&lt;br /&gt;
|-&lt;br /&gt;
| 17-23&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 24-25&lt;br /&gt;
| Scale down the input image using a box filter. 0 = No downscale, 1 = 2x1 downscale. 2 = 2x2 downscale, 3 = invalid&lt;br /&gt;
|-&lt;br /&gt;
| 31-26&lt;br /&gt;
| Not writable&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== TextureCopy ===&lt;br /&gt;
&lt;br /&gt;
When bit 3 of the control register is set, the hardware performs a TextureCopy-mode transfer: no format conversions are done, instead a raw data copy is performed from the source to the destination, with a configurable gap between lines. All bits of the control register are ignored, except for input/output dimensions, which are used for line width and gap, and bit 2, which must be set when gaps are used.&lt;br /&gt;
&lt;br /&gt;
The total amount of bytes to copy is specified in the size register, the hardware loops reading lines from the input and writing them to the output until this amount is copied. The gap specifies the number of bytes to skip after each line read (a gap of 0 results in a contiguous read). Gaps do not count towards the total size of the transfer.&lt;br /&gt;
&lt;br /&gt;
When setting line width and gap they must be divided by 2 (it can be thought as the calculation being done in bits, and the values being stripped of their lower 4 bits for the alignment). For example, if the left half of a 32x32 RGB8 texture is to be copied, the parameters will be:&lt;br /&gt;
 line width = (16 * 24) &amp;gt;&amp;gt; 4 = 24&lt;br /&gt;
 gap = line width&lt;br /&gt;
 size = 16 * 32 * 3 = 1536&lt;br /&gt;
&lt;br /&gt;
By correctly calculating the input and output gap sizes it is possible to use this functionality to copy arbitrary sub-rectangles between differently-sized framebuffers or textures, which is one of its main uses over a regular no-conversion DisplayTransfer. When copying tiled textures/framebuffers it&#039;s important to remember that the contents of a tile are laid out sequentially in memory, and so this should be taken into account when calculating the transfer parameters.&lt;br /&gt;
&lt;br /&gt;
Specifying invalid/junk values for the TextureCopy dimensions can result in the GPU hanging while attempting to process this TextureCopy. For instance, when in contiguous mode the size must be at least 16; when in gap mode, the size must be at least 192, and the line width must not be 0.&lt;br /&gt;
&lt;br /&gt;
== Command List ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| Buffer size in bytes &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E8&lt;br /&gt;
| Buffer physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018F0&lt;br /&gt;
| Setting bit0 to 1 enables processing GPU command execution. Upon completion, bit0 seems to be reset to 0.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These 3 registers are used by [[GSP_Shared_Memory|GX command]] 1. This is used for [[GPU/Internal_Registers|GPU commands]].&lt;br /&gt;
&lt;br /&gt;
== Framebuffers ==&lt;br /&gt;
These LCD framebuffers normally contain the last rendered frames from the GPU. The framebuffers are drawn from left-to-right, instead of top-to-bottom.(Thus the beginning of the framebuffer is drawn starting at the left side of the screen)&lt;br /&gt;
&lt;br /&gt;
Both of the 3D screen left/right framebuffers are displayed regardless of the 3D slider&#039;s state, however when the 3D slider is set to &amp;quot;off&amp;quot; the 3D effect is disabled. Normally when the 3D slider&#039;s state is set to &amp;quot;off&amp;quot; the left/right framebuffer addresses are set to the same physical address. When the 3D effect is disabled and the left/right framebuffers are set to separate addresses, the LCD seems to alternate between displaying the left/right framebuffer each frame.&lt;br /&gt;
&lt;br /&gt;
==== Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00400 = 0x1C2&lt;br /&gt;
* 0x1EF00404 = 0xD1&lt;br /&gt;
* 0x1EF00408 = 0x1C1&lt;br /&gt;
* 0x1EF0040C = 0x1C1&lt;br /&gt;
* 0x1EF00410 = 0&lt;br /&gt;
* 0x1EF00414 = 0xCF&lt;br /&gt;
* 0x1EF00418 = 0xD1&lt;br /&gt;
* 0x1EF0041C = 0x1C501C1&lt;br /&gt;
* 0x1EF00420 = 0x10000&lt;br /&gt;
* 0x1EF00424 = 0x19D&lt;br /&gt;
* 0x1EF00428 = 2&lt;br /&gt;
* 0x1EF0042C = 0x1C2&lt;br /&gt;
* 0x1EF00430 = 0x1C2&lt;br /&gt;
* 0x1EF00434 = 0x1C2&lt;br /&gt;
* 0x1EF00438 = 1&lt;br /&gt;
* 0x1EF0043C = 2&lt;br /&gt;
* 0x1EF00440 = 0x1960192&lt;br /&gt;
* 0x1EF00444 = 0&lt;br /&gt;
* 0x1EF00448 = 0&lt;br /&gt;
* 0x1EF0045C = 0x19000F0&lt;br /&gt;
* 0x1EF00460 = 0x1c100d1&lt;br /&gt;
* 0x1EF00464 = 0x1920002&lt;br /&gt;
* 0x1EF00470 = 0x80340&lt;br /&gt;
* 0x1EF0049C = 0&lt;br /&gt;
&lt;br /&gt;
==== More Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00468 = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF0046C = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF00494 = 0x18300000&lt;br /&gt;
* 0x1EF00498 = 0x18300000&lt;br /&gt;
* 0x1EF00478 = 1, doesn&#039;t stay 1, read as 0&lt;br /&gt;
* 0x1EF00474 = 0x10501&lt;br /&gt;
&lt;br /&gt;
[[Category:GPU]]&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23389</id>
		<title>GSP Shared Memory</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23389"/>
		<updated>2025-03-24T12:33:27Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the structure of the GSP [[GSPGPU:RegisterInterruptRelayQueue|shared]] memory. Interrupt, framebuffer, and GX command data is stored here.&lt;br /&gt;
&lt;br /&gt;
=Interrupt Queue=&lt;br /&gt;
&lt;br /&gt;
The Interrupt queue is located at sharedMemBase + (clientID * 0x40).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| Offset from the count where to save incoming interrupts&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| Count (max 0x20 for PDC, 0x34 for others)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| Missed other interrupts (set to 1 when 0 and count &amp;gt;= 0x34)&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| Flags (bit0 = skip PDC)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4-0x7&lt;br /&gt;
| Missed PDC0 (incremented when flags.bit0 is clear and count &amp;gt;= 0x20) &lt;br /&gt;
|-&lt;br /&gt;
| 0x8-0xB&lt;br /&gt;
| Missed PDC1 (same as above)&lt;br /&gt;
|-&lt;br /&gt;
| 0xC-0x3F&lt;br /&gt;
| Interrupt list (u8) (0=PSC0, 1=PSC1, 2=PDC0/VBlankTop, 3=PDC1/VBlankBottom, 4=PPF, 5=P3D, 6=DMA)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP fills the interrupt list, then triggers the event set with [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]] for the specified process(es).&lt;br /&gt;
&lt;br /&gt;
PDC interrupts are sent to all processes; other interrupts are only sent to the process with GPU rights.&lt;br /&gt;
&lt;br /&gt;
= Framebuffer Info =&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the top LCD is located at sharedMemBase + 0x200 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the bottom LCD is located at sharedMemBase + 0x240 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
|  Framebuffer info [[GSPGPU:SetBufferSwap|entry]] index&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Flags (bit0 = client has set new data)&lt;br /&gt;
|-&lt;br /&gt;
| 3-2&lt;br /&gt;
| Padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Structure ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Active framebuffer (0 = first, 1 = second)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Left framebuffer VA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Right framebuffer VA (top screen only)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Stride]] (offset 0x90)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| [[GPU/External_Registers#Framebuffer_format|Format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Status]] (offset 0x78)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ? (&amp;quot;Attribute&amp;quot;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a process sets this framebuffer info, it sets index to &amp;lt;nowiki&amp;gt;(index+1) &amp;amp; 1&amp;lt;/nowiki&amp;gt;. Then it writes the framebuffer info entry, and sets flag to value 1. The GSP module loads this framebuffer info entry data into GSP state once the [[GPU]] finishes processing GX commands 3 or 4. Once the GSP module finishes loading this framebuffer info, it sets flag to value 0, then it will not load the framebuffer info again until flag is value 1. After loading this entry data into GSP state, the GSP module then writes this framebuffer state to the [[LCD]] registers. GSP module automatically updates the LCD framebuffer registers each time GX commands 3 or 4 finish, even when this shared memory data was not updated by the application.(GSP module toggles the active framebuffer register when automatically updating LCD registers, when shared memory data is not used)&lt;br /&gt;
&lt;br /&gt;
The two 0x1C-byte framebuffer info entries are located at framebufferinfo+4.&lt;br /&gt;
&lt;br /&gt;
= 3D Slider and 3D [[GSPGPU:SetLedForceOff|LED]] =&lt;br /&gt;
&lt;br /&gt;
See [[Configuration Memory]].&lt;br /&gt;
&lt;br /&gt;
= Command Queue =&lt;br /&gt;
&lt;br /&gt;
The command queue is located at sharedMemBase + 0x800 + (clientID * 0x200). It consists of an header followed by at most 15 command entries. Each command entry is of size 0x20 and has an header followed by command specific parameters.&lt;br /&gt;
&lt;br /&gt;
After adding a command, [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]] must be used to start command processing (official code does so when the total commands field is 1).&lt;br /&gt;
&lt;br /&gt;
== Command Queue Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Index of the command to process, this is incremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Total commands to process, this is incremented by the application when adding the command to the queue, and decremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Flags (bit0 = completed, bit7 = fatal error)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When bit0 is set, further processing of commands is halted until the client resets the flag and calls [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 7-4&lt;br /&gt;
| Result code for the last command which failed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Command Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| When bit0 is set, GSP stops processing further commands (can be used for packing together sets of commands)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When set, the command fails if GSP is busy handling any other command; otherwise, it only fails if GSP is busy handling a command of the same kind&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Commands ==&lt;br /&gt;
&lt;br /&gt;
Addresses specified in parameters are virtual addresses. For applications these are normally located in GSP memory, while for other processes they are located in VRAM.&lt;br /&gt;
&lt;br /&gt;
Address and size parameters except for command 0 and command 5 must be 8-byte aligned.&lt;br /&gt;
&lt;br /&gt;
=== Trigger DMA Request ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x00)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush source (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command is normally used to DMA data from the application GSP [[Memory_layout|heap]] to VRAM. When flushing is enabled and the source buffer is not located within VRAM, svcFlushProcessDataCache is used to flush the source buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Command List Processing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x01)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Update gas additive blend results (0 = don&#039;t update, 1 = update)&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush buffer (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified address to a physical address, then writes the physical address and size to the [[GPU]] registers at 0x1EF018E0. This buffer contains [[GPU/Internal_Registers|GPU commands]]. When flushing is enabled, svcFlushProcessDataCache is used to flush the buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Memory Fill ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x02)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 start address (0 = don&#039;t fill anything)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 value&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf0 end address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 start address (0 = don&#039;t fill anything)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf1 value&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf1 end address&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Control0 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (Control1 &amp;lt;&amp;lt; 16)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these addresses and the specified parameters to the [[GPU]] registers at 0x1EF00010 and 0x1EF00020. Doing so fills the specified buffers with the associated 4-byte value. This is used to clear GPU framebuffers.&lt;br /&gt;
The associated buffer address must not be &amp;lt;= to the main buffer address, thus the associated buffer address must not be zero as well. When the bufX address is zero, processing for the bufX parameters is skipped.&lt;br /&gt;
&lt;br /&gt;
The values of Control0 and Control1 give information about the type of memory fill. See [[GPU/External_Registers#Memory Fill|here]] for more information about memory fill parameters.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Display Transfer ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x03)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Input framebuffer [[GPU|dimensions]]&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Output framebuffer dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU|Flags]], for applications this is 0x1001000 for the main screen, and 0x1000 for the sub screen.&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these physical addresses and parameters to the [[GPU]] registers at 0x1EF00C00. This GPU command copies the already rendered framebuffer data from the input GPU framebuffer address to the specified output LCD framebuffer. The input framebuffer is normally located in VRAM.&lt;br /&gt;
&lt;br /&gt;
The GPU color buffer is stored in the same Z-curve (tiled) format as textures. By default, SetDisplayTransfer converts the given buffer from the tiled format to a linear format adapted to the LCD framebuffers.&lt;br /&gt;
&lt;br /&gt;
Display transfers are performed asynchronously, so after requesting a display transfer you should wait for the PPF interrupt to fire before reading the output data.&lt;br /&gt;
&lt;br /&gt;
The minimum supported dimension for output is 64x64, anything lower will hang the engine.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Texture Copy ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x04)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Total bytes to copy, not including gaps.&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Bits 0-15: Size of input line, in bytes. Bits 16-31: Gap between input lines, in bytes.&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Same as 4, but for the output.&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Flags, corresponding to the [[GPU/External_Registers#Transfer_Engine|Transfer Engine flags]]. However, for TextureCopy commands, bit 3 is always set, bit 2 is set if any output dimension is smaller than the input, and other bits are always 0.&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command is similar to cmd3. It also triggers the [[GPU/External_Registers#Transfer_Engine|GPU Transfer Engine]], but setting the TextureCopy parameters.&lt;br /&gt;
&lt;br /&gt;
=== Flush Cache Regions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x05)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf1 address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 size&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf2 address&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf2 size&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The application buffer addresses specified in the parameters are used with [[SVC|svcFlushProcessDataCache]]. The input buf0 size must not be zero. When buf1 size is zero, svcFlushProcessDataCache() for buf1 and buf2 are skipped. When buf2 size is zero, svcFlushProcessDataCache() for buf2 is skipped.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23388</id>
		<title>GSP Shared Memory</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=23388"/>
		<updated>2025-03-24T12:27:48Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Command queue stuff&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the structure of the GSP [[GSPGPU:RegisterInterruptRelayQueue|shared]] memory. Interrupt, framebuffer, and GX command data is stored here.&lt;br /&gt;
&lt;br /&gt;
=Interrupt Queue=&lt;br /&gt;
&lt;br /&gt;
The Interrupt queue is located at sharedMemBase + (clientID * 0x40).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| Offset from the count where to save incoming interrupts&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| Count (max 0x20 for PDC, 0x34 for others)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| Missed other interrupts (set to 1 when 0 and count &amp;gt;= 0x34)&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| Flags (bit0 = skip PDC)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4-0x7&lt;br /&gt;
| Missed PDC0 (incremented when flags.bit0 is clear and count &amp;gt;= 0x20) &lt;br /&gt;
|-&lt;br /&gt;
| 0x8-0xB&lt;br /&gt;
| Missed PDC1 (same as above)&lt;br /&gt;
|-&lt;br /&gt;
| 0xC-0x3F&lt;br /&gt;
| Interrupt list (u8) (0=PSC0, 1=PSC1, 2=PDC0/VBlankTop, 3=PDC1/VBlankBottom, 4=PPF, 5=P3D, 6=DMA)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP fills the interrupt list, then triggers the event set with [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]] for the specified process(es).&lt;br /&gt;
&lt;br /&gt;
PDC interrupts are sent to all processes; other interrupts are only sent to the process with GPU rights.&lt;br /&gt;
&lt;br /&gt;
= Framebuffer Info =&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the top LCD is located at sharedMemBase + 0x200 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the bottom LCD is located at sharedMemBase + 0x240 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
|  Framebuffer info [[GSPGPU:SetBufferSwap|entry]] index&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Flags (bit0 = client has set new data)&lt;br /&gt;
|-&lt;br /&gt;
| 3-2&lt;br /&gt;
| Padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Structure ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Active framebuffer (0 = first, 1 = second)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Left framebuffer VA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Right framebuffer VA (top screen only)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Stride]] (offset 0x90)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| [[GPU/External_Registers#Framebuffer_format|Format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Status]] (offset 0x78)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ? (&amp;quot;Attribute&amp;quot;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a process sets this framebuffer info, it sets index to &amp;lt;nowiki&amp;gt;(index+1) &amp;amp; 1&amp;lt;/nowiki&amp;gt;. Then it writes the framebuffer info entry, and sets flag to value 1. The GSP module loads this framebuffer info entry data into GSP state once the [[GPU]] finishes processing GX commands 3 or 4. Once the GSP module finishes loading this framebuffer info, it sets flag to value 0, then it will not load the framebuffer info again until flag is value 1. After loading this entry data into GSP state, the GSP module then writes this framebuffer state to the [[LCD]] registers. GSP module automatically updates the LCD framebuffer registers each time GX commands 3 or 4 finish, even when this shared memory data was not updated by the application.(GSP module toggles the active framebuffer register when automatically updating LCD registers, when shared memory data is not used)&lt;br /&gt;
&lt;br /&gt;
The two 0x1C-byte framebuffer info entries are located at framebufferinfo+4.&lt;br /&gt;
&lt;br /&gt;
= 3D Slider and 3D [[GSPGPU:SetLedForceOff|LED]] =&lt;br /&gt;
&lt;br /&gt;
See [[Configuration Memory]].&lt;br /&gt;
&lt;br /&gt;
= Command Queue =&lt;br /&gt;
&lt;br /&gt;
The command queue is located at sharedMemBase + 0x800 + (clientID * 0x200). It consists of an header followed by at most 15 command entries. Each command entry is of size 0x20 and has an header followed by command specific parameters.&lt;br /&gt;
&lt;br /&gt;
After adding a command, [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]] must be used to start command processing (official code does so when the total commands field is 1).&lt;br /&gt;
&lt;br /&gt;
== Command Queue Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Index of the command to process, this is incremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Total commands to process, this is incremented by the application when adding the command to the queue, and decremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Flags (bit0 = completed, bit7 = fatal error)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When bit0 is set, further processing of commands is halted until the client resets the flag and calls [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Result code for the last command which failed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Command Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| When bit0 is set, GSP stops processing further commands (can be used for packing together sets of commands)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When set, the command fails if GSP is busy handling any other command; otherwise, it only fails if GSP is busy handling a command of the same kind&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Commands ==&lt;br /&gt;
&lt;br /&gt;
Addresses specified in parameters are virtual addresses. For applications these are normally located in GSP memory, while for other processes they are located in VRAM.&lt;br /&gt;
&lt;br /&gt;
Address and size parameters except for command 0 and command 5 must be 8-byte aligned.&lt;br /&gt;
&lt;br /&gt;
=== Trigger DMA Request ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x00)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush source (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command is normally used to DMA data from the application GSP [[Memory_layout|heap]] to VRAM. When flushing is enabled and the source buffer is not located within VRAM, svcFlushProcessDataCache is used to flush the source buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Command List Processing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x01)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Update gas additive blend results (0 = don&#039;t update, 1 = update)&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush buffer (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified address to a physical address, then writes the physical address and size to the [[GPU]] registers at 0x1EF018E0. This buffer contains [[GPU/Internal_Registers|GPU commands]]. When flushing is enabled, svcFlushProcessDataCache is used to flush the buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Memory Fill ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x02)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 start address (0 = don&#039;t fill anything)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 value&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf0 end address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 start address (0 = don&#039;t fill anything)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf1 value&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf1 end address&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Control0 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (Control1 &amp;lt;&amp;lt; 16)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these addresses and the specified parameters to the [[GPU]] registers at 0x1EF00010 and 0x1EF00020. Doing so fills the specified buffers with the associated 4-byte value. This is used to clear GPU framebuffers.&lt;br /&gt;
The associated buffer address must not be &amp;lt;= to the main buffer address, thus the associated buffer address must not be zero as well. When the bufX address is zero, processing for the bufX parameters is skipped.&lt;br /&gt;
&lt;br /&gt;
The values of Control0 and Control1 give information about the type of memory fill. See [[GPU/External_Registers#Memory Fill|here]] for more information about memory fill parameters.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Display Transfer ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x03)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Input framebuffer [[GPU|dimensions]]&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Output framebuffer dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU|Flags]], for applications this is 0x1001000 for the main screen, and 0x1000 for the sub screen.&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these physical addresses and parameters to the [[GPU]] registers at 0x1EF00C00. This GPU command copies the already rendered framebuffer data from the input GPU framebuffer address to the specified output LCD framebuffer. The input framebuffer is normally located in VRAM.&lt;br /&gt;
&lt;br /&gt;
The GPU color buffer is stored in the same Z-curve (tiled) format as textures. By default, SetDisplayTransfer converts the given buffer from the tiled format to a linear format adapted to the LCD framebuffers.&lt;br /&gt;
&lt;br /&gt;
Display transfers are performed asynchronously, so after requesting a display transfer you should wait for the PPF interrupt to fire before reading the output data.&lt;br /&gt;
&lt;br /&gt;
The minimum supported dimension for output is 64x64, anything lower will hang the engine.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Texture Copy ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x04)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Total bytes to copy, not including gaps.&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Bits 0-15: Size of input line, in bytes. Bits 16-31: Gap between input lines, in bytes.&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Same as 4, but for the output.&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Flags, corresponding to the [[GPU/External_Registers#Transfer_Engine|Transfer Engine flags]]. However, for TextureCopy commands, bit 3 is always set, bit 2 is set if any output dimension is smaller than the input, and other bits are always 0.&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command is similar to cmd3. It also triggers the [[GPU/External_Registers#Transfer_Engine|GPU Transfer Engine]], but setting the TextureCopy parameters.&lt;br /&gt;
&lt;br /&gt;
=== Flush Cache Regions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command header (ID = 0x05)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf1 address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 size&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf2 address&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf2 size&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The application buffer addresses specified in the parameters are used with [[SVC|svcFlushProcessDataCache]]. The input buf0 size must not be zero. When buf1 size is zero, svcFlushProcessDataCache() for buf1 and buf2 are skipped. When buf2 size is zero, svcFlushProcessDataCache() for buf2 is skipped.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=IPC&amp;diff=23054</id>
		<title>IPC</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=IPC&amp;diff=23054"/>
		<updated>2025-01-19T12:07:30Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The 3DS software stack follows a server/client architecture, where common functionality is provided by global server processes through interfaces called &#039;&#039;ports&#039;&#039; and &#039;&#039;services&#039;&#039;. Applications can access this functionality by creating &#039;&#039;sessions&#039;&#039; to the provided services. Technically, this is implemented using &#039;&#039;interprocess communication&#039;&#039; (IPC).&lt;br /&gt;
&lt;br /&gt;
== Concepts ==&lt;br /&gt;
&lt;br /&gt;
=== Sessions ===&lt;br /&gt;
&lt;br /&gt;
Sessions are communication channels consisting of a client and server, through which data can be exchanged in the form of a request and response, respectively. Through sessions, the standard [[#Command Structure|IPC command protocol]] is implemented. Clients use their client session handle to send IPC commands to the server using svcSendSyncRequest, while servers use svcReplyAndReceive to reply. Client sessions can&#039;t be used with svcReplyAndReceive. In both cases, the kernel takes care of transferring IPC command data from the [[Thread Local Storage]] of the sending thread to the TLS of the receiving thread. Sessions can be created through svcCreateSession, which provides the caller with the client and server handles.&lt;br /&gt;
&lt;br /&gt;
Sessions are used in their raw form to implement [[FS:OpenFile|file handles]]. In this case, fs-module creates a raw session using svcCreateSession, and provides the &amp;quot;fs:USER&amp;quot; client with the resulting client session handle, keeping the server session handle for its own use. These sessions expose their [[Filesystem services#File service|own set of IPC commands]] which act on the file that was opened through fs:USER.&lt;br /&gt;
&lt;br /&gt;
=== Ports ===&lt;br /&gt;
&lt;br /&gt;
Ports are interfaces for managing multiple client sessions to the same high-level server. Ports are created through svcCreatePort, and can be global or private. A global port is created by passing a name for the port to svcCreatePort. Clients can start IPC sessions to global ports by connecting to the port using svcConnectToPort, passing the desired port name. If the port is private, it is not possible to create a session through svcConnectToPort - sessions can only be created if one has a handle to the port itself, obtained from the call to svcCreatePort. When a client wishes to connect to a port (i.e. create a session), the server must accept the new session using svcAcceptSession. The kernel notifies the server whenever a new session is incoming via the server&#039;s port handle. The server can wait for this notification through svcWaitSynchronization or svcReplyAndReceive.&lt;br /&gt;
&lt;br /&gt;
The two global ports usually found on retail are &amp;quot;srv:&amp;quot; ([[Services|service manager]]) and &amp;quot;err:f&amp;quot; ([[ErrDisp]]).&lt;br /&gt;
&lt;br /&gt;
=== Services ===&lt;br /&gt;
&lt;br /&gt;
Services are an abstraction of ports that are managed by service manager. Services cannot be connected to through svcConnectToPort, as the underlying port is a private port. The service name is instead entirely handled by service manager, and the kernel is not aware of it. Clients are instead expected to open a session to a service using the &amp;quot;srv:&amp;quot; port command [[SRV:GetServiceHandle|GetServiceHandle]]. The client process must pass an access permission check for each service (by name) that it attempts to request a session with. These permissions are granted in the &amp;quot;service access control&amp;quot; of the title&#039;s [[NCCH/Extended Header#ARM11 Local System Capabilities|extended header]]. A service is registered with service manager using the command [[SRV:RegisterService|RegisterService]].&lt;br /&gt;
&lt;br /&gt;
Internally, service manager creates services by creating a private port which it associates with the desired service name. The resulting server port handle is returned in the RegisterService response, for the server&#039;s own use (see ports). To create sessions to a service on behalf of a client, service manager uses svcCreateSessionToPort, passing the client port handle it obtained when creating the port, to the kernel. Service manager then returns the resulting session handle to the client in the GetServiceHandle response.&lt;br /&gt;
&lt;br /&gt;
The majority of 3DS inter-process communication is implemented as services.&lt;br /&gt;
&lt;br /&gt;
=== Shared Memory ===&lt;br /&gt;
&lt;br /&gt;
Communication through port/service requests and replies may incur a big bottleneck when exchanging large amounts of data because the kernel needs to transfer the data between the two involved processes. There is hence a complementary feature to share the same physical memory between two processes. For this purpose, one process needs to create a block of shared memory using svcCreateMemoryBlock such that the other process can map it into its own virtual memory address space using svcMapMemoryBlock. The memory block handle for the latter is provided using a regular IPC command.&lt;br /&gt;
&lt;br /&gt;
Many services use shared memory as a secondary command interface (e.g. [[GSP_Shared_Memory|GSP]]), the processing of which is triggered through an IPC request (cf. [[GSPGPU:TriggerCmdReqQueue]]).&lt;br /&gt;
&lt;br /&gt;
== Message Structure ==&lt;br /&gt;
&lt;br /&gt;
IPC requests are written to the [[Thread Local Storage]] at offset 0x80 and submitted using [[SVC|svcSendSyncRequest]]. If the kernel was able to dispatch the request, the server reply will be written to TLS+0x80 before svcSendSyncRequest returns. By convention, the second word of the response data is an error code (or 0 on success). IPC requests and IPC replies follow the same structure.&lt;br /&gt;
&lt;br /&gt;
Every IPC message starts with a u32 header code. Parameters (if any) are written following this header. There are &amp;quot;normal parameters&amp;quot;, which are fixed-size words, and there are &amp;quot;translate parameters&amp;quot;, which are of flexible size and each of which begins with a header. The entire command has the following structure:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Word&lt;br /&gt;
!  Size&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| 1&lt;br /&gt;
| Header code&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| x&lt;br /&gt;
| Normal parameters&lt;br /&gt;
|-&lt;br /&gt;
| 1+x&lt;br /&gt;
| y&lt;br /&gt;
| Translate parameters&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The IPC message header has the following structure:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bits&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0-5&lt;br /&gt;
| Total size in words of the translate parameters (=y). Note that this is in general different from the number of translate parameters&lt;br /&gt;
|-&lt;br /&gt;
| 6-11&lt;br /&gt;
| Number of normal parameters (=x)&lt;br /&gt;
|-&lt;br /&gt;
| 12-15&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 16-31&lt;br /&gt;
| Command ID&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When calling svcSendSyncRequest, the command id specifies a function specific to the target service to call. When calling svcReplyAndReceive, the command ID is generally ignored. However if it&#039;s 0xffff (and 0 is passed for the reply target handle), the kernel will omit the IPC reply and just wait for incoming IPC requests.&lt;br /&gt;
&lt;br /&gt;
Each translate parameter starts with a translation descriptor:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bits&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 1-3&lt;br /&gt;
| Translation type&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Translate parameters are modified/translated transparently by the kernel. They are used to transfer handles/buffers between the different processes.&lt;br /&gt;
&lt;br /&gt;
The type of parameter is described by the bits 1-3 in the translation descriptor. Parameter types accepted for sending by the kernel are: 0, 1, 2, 5, 6, 7. &lt;br /&gt;
&lt;br /&gt;
For replies, only 0, 1, 5, 6, 7 are allowed. In other words any type 2 fields must be zeroed before calling svcReplyAndReceive on the server-side. For replies, type 0, 1, and 2 are ignored. Types 5, 6, and 7 do something with the mem pointer upon reply. The type 0 descriptor can be used to ignore parameters. The number of parameters covered by a type-0 descriptor is (desc &amp;gt;&amp;gt; 26) + 1.&lt;br /&gt;
&lt;br /&gt;
=== Handle Translation ===&lt;br /&gt;
&lt;br /&gt;
Translation type 0 is used to send handles across processes. The corresponding translation descriptor has the following structure:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bits&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| If set, the handles are closed for the caller.&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| If set, the following handles are replaced by the process ID. Otherwise, translate each handle between client and server.&lt;br /&gt;
|-&lt;br /&gt;
| 26-31&lt;br /&gt;
| Number of handles following this descriptor (minus one).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Setting both bit4 and bit5 causes a kernel panic. Usage examples:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! scope=&amp;quot;col&amp;quot; width=&amp;quot;300&amp;quot; |  Usual form&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00000000 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; ((num_handles-1)&amp;lt;&amp;lt;26)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;handle 0&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;handle 1&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
| Copies the given KHandles to the receiving process, i.e. creating new handles in the target process while keeping around the ones of the source process. When a handle value is 0x0, value 0x0 is written to the destination cmdbuf without doing any actual handle-transfer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x00000010 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; ((num_handles-1)&amp;lt;&amp;lt;26)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;handle 0&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;handle 1&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
| Moves the given KHandles to the receiving process, i.e. creating new handles in the target process and closing the ones of the source process. When a handle value is 0x0, value 0x0 is written to the destination cmdbuf without doing any actual handle-transfer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x00000020&lt;br /&gt;
&amp;lt;placeholder&amp;gt;&lt;br /&gt;
| Let kernel set value to calling process ProcessID.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Static Buffer Translation ===&lt;br /&gt;
&lt;br /&gt;
Each thread may set up up to 16 static buffer descriptors in their [[Thread Local Storage]] at offset 0x180. These contain information about buffers in the thread&#039;s memory space that may be used for information exchange for communication with other processes. In particular, a static buffer descriptor encodes the address and size of a buffer.&lt;br /&gt;
&lt;br /&gt;
Using IPC requests, data can be transferred from any location in the source process to one of the static buffers set up in the target process. This is done using a translation descriptor of type 1, which is followed by a pointer to the source data. The translation descriptor has the following structure:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bits&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 1-3&lt;br /&gt;
| Translation type (characteristically 1)&lt;br /&gt;
|-&lt;br /&gt;
| 10-13&lt;br /&gt;
| Static buffer index of the receiving process &lt;br /&gt;
|-&lt;br /&gt;
| 14-31&lt;br /&gt;
| Size in bytes of the transferred data. Specifying an amount larger than the target static buffer will result in a kernelpanic. &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When encountering such a translation descriptor, the kernel will look up the static buffer address and size corresponding to the given index and (if the buffer can hold the requested amount of data) will copy the data to the specified location.&lt;br /&gt;
&lt;br /&gt;
Note that the translation descriptor and static buffer descriptor share the same layout. However, it is unknown whether the kernel ever reads fields other than the buffer address and size when dealing with static buffer descriptors.&lt;br /&gt;
&lt;br /&gt;
Usage examples:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! scope=&amp;quot;col&amp;quot; width=&amp;quot;300&amp;quot; |  Usual form&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00000002 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (size&amp;lt;&amp;lt;14) &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (static_buffer_id&amp;lt;&amp;lt;10)&lt;br /&gt;
&amp;lt;ptr&amp;gt;&lt;br /&gt;
| The corresponding value contains a ptr to a buffer of said size, that should be copied to an already set-up buffer in destination process at [[Thread Local Storage]] offset 0x180 + static_buffer_id*8. The static_buffer_id is only 4 bits, making it possible for at most up to 16 buffers in total per thread.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Buffer Mapping Translation ===&lt;br /&gt;
&lt;br /&gt;
The IPC subsystem can temporarily map a whole buffer of the sender&#039;s memory into the receiver&#039;s memory space. This is useful for large buffers, for which the overhead of copying static buffer data around would be too expensive.&lt;br /&gt;
&lt;br /&gt;
This kind of translation is enabled by setting bit3 in the translation descriptor. The other two bits of what&#039;s documented as the translation type above are used to specify buffer access permissions of the source process.&lt;br /&gt;
&lt;br /&gt;
Buffers will get mapped at virtual address 0x04000000+ in the destination process. When this translation descriptor is submitted to the kernel through svcReplyAndReceive, the given buffer will be unmapped from the sending process(otherwise the buffer will be left mapped after the cmd-reply is finished). Regardless of the descriptor used here, the MMU-table entries for the source-process(from svcSendSyncRequest) buffers are not changed: memory permissions are left at the original while commands are being processed. The memory permissions for buffers at 0x04000000+ is always RW-, regardless of the actual memory permissions for the source-process buffer. Bitmask 0xFFF(low 12-bits) of the start address of each buffer for 0x04000000+ is the same as bitmask 0xFFF from the source-process buffer address.&lt;br /&gt;
&lt;br /&gt;
The buffer address written into the destination cmdbuf by the kernel with svcSendSyncRequest is the allocated 0x04000000+ buffer. When doing the same with svcSendSyncRequest, the buffer address is the same one from the source cmdbuf(0x04000000+).&lt;br /&gt;
&lt;br /&gt;
The first and last pages of the buffer at 0x04000000+ are allocated under the BASE memregion(with data being copied to/from the original source-process buffer as needed), with the rest being mapped to the original buffer physmem. When the source-process buffer is 0x1000-byte aligned, the first page for 0x04000000+ is mapped directly into the original buffer physmem instead of allocating BASE memory(likewise for the last page when the buffer size is 0x1000-byte aligned).&lt;br /&gt;
&lt;br /&gt;
Each buffer at 0x04000000+ has 1 page of unmapped memory before and after the mapped memory, used for separating each buffer. Hence, the first buffer&#039;s page at 0x04000000+ is always mapped starting at 0x04001000 not 0x04000000.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bits&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 1-2&lt;br /&gt;
| Access permission flags for the source process: 1=read-only, 2=write-only, 3=read/write. Specifying 0 will cause a kernel panic.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Characteristically 1 for this translation type.&lt;br /&gt;
|-&lt;br /&gt;
| 4-31&lt;br /&gt;
| Size in bytes of the shared memory block.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Usage examples:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! scope=&amp;quot;col&amp;quot; width=&amp;quot;300&amp;quot; |  Usual form&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00000008&lt;br /&gt;
| This command will cause a kernel panic.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;nowiki&amp;gt;0x0000000A | (size&amp;lt;&amp;lt;4)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&amp;lt;ptr&amp;gt;&lt;br /&gt;
| The corresponding value contains a ptr to a buffer of said size. The buffer specified by the source process must have read permission(tested on hardware with a read-only buffer). Used for input buffers.&lt;br /&gt;
|-&lt;br /&gt;
| 0x0000000C &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (size&amp;lt;&amp;lt;4)&lt;br /&gt;
&amp;lt;ptr&amp;gt;&lt;br /&gt;
| The corresponding value contains a ptr to a buffer of said size. The buffer specified by the source process must have write permission. Used for output buffers. In the destination process with the buffer mapped at 0x04000000+, that buffer has same content as the buffer from the source buffer(like descriptor 0x0000000A). When handling command requests this is handled the same way as 0x0000000A, besides the descriptor type written into the dst cmdbuf and memory permissions.&lt;br /&gt;
|-&lt;br /&gt;
| 0x0000000E &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (size&amp;lt;&amp;lt;4)&lt;br /&gt;
&amp;lt;ptr&amp;gt;&lt;br /&gt;
| The corresponding value contains a ptr to a buffer of said size. The buffer specified by the source process must have read permission during cmd-request handling(write permission is checked during cmd-reply handling for the original buffer). Some calls like [[CECDU:WriteMessageWithHMAC]] use it, however it is unsure as to why. When handling command requests this is handled the same way as 0x0000000A, and for handling command replies this is handled the same way as 0x0000000C(besides the descriptor type written into the dst cmdbuf for both of these).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Usage Examples for other Translation Types ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Type&lt;br /&gt;
! scope=&amp;quot;col&amp;quot; width=&amp;quot;300&amp;quot; |  Usual form&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0x00000004 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (size&amp;lt;&amp;lt;8) &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (static_buffer_id&amp;lt;&amp;lt;4)&lt;br /&gt;
&amp;lt;ptr&amp;gt;&lt;br /&gt;
| This is typically used for RW buffers over PXI, but any process can use this. The address written to the destination cmd-buf is a phys-addr for a table located in the corresponding static buffer of the receiving process (which must be provided by the latter, otherwise the kernel dereferences NULL). Each static buffer needs to be page-aligned and musn&#039;t exceed a page&#039;s length (kernelpanic otherwise). This table contains the phys-addrs for the actual data, the array entries have the following format: {u32 *datachunk_physaddr, u32 datachunk_bytesize}.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0x00000006 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (size&amp;lt;&amp;lt;8) &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (static_buffer_id&amp;lt;&amp;lt;4)&lt;br /&gt;
&amp;lt;ptr&amp;gt;&lt;br /&gt;
| Same as above except for read-only buffer. Prior(?) to the kernel version which implemented memory-permission checking for PXI buffers, this was unused and hence triggered a kernelpanic.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== SVCs ==&lt;br /&gt;
&lt;br /&gt;
=== svcReplyAndReceive ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Signature:&#039;&#039;&#039;&lt;br /&gt;
 Result ReplyAndReceive(s32* index, Handle* handles, s32 handleCount, Handle replyTarget)&lt;br /&gt;
&lt;br /&gt;
In a single operation, sends a IPC reply and waits for a new request. &amp;lt;code&amp;gt;handles&amp;lt;/code&amp;gt; should be a pointer to an array of &amp;lt;code&amp;gt;handleCount&amp;lt;/code&amp;gt; handles.&amp;lt;sup&amp;gt;TODO: Are only port/session handles supported?&amp;lt;/sup&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;replyTarget&amp;lt;/code&amp;gt; should contain a handle to the session to send the reply to. (This is usually the session from which we received the previous request.)&lt;br /&gt;
If &amp;lt;code&amp;gt;replyTarget&amp;lt;/code&amp;gt; is 0, no reply and the call will simply wait for an incoming event.&amp;lt;sup&amp;gt;TODO: It doesn&#039;t seem like the 0xFFFF0000 command id mentioned in the above sections is necessary, but needs confirmation.&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Upon returning, &amp;lt;code&amp;gt;index&amp;lt;/code&amp;gt; will contain an index into &amp;lt;code&amp;gt;handles&amp;lt;/code&amp;gt; specifying which object changed state.&lt;br /&gt;
If it&#039;s a server port endpoint, it means that there is a new incoming connection on that port which should be accepted using svcAcceptSession.&lt;br /&gt;
If it&#039;s a server session endpoint it means that we received a request from that session and should process and then reply to it by calling svcReplyAndReceive again with &amp;lt;code&amp;gt;replyTarget&amp;lt;/code&amp;gt; set to that session&#039;s handle.&lt;br /&gt;
&lt;br /&gt;
An example of a server svcReplyAndReceive loop is:&lt;br /&gt;
&lt;br /&gt;
 #define MAX_CLIENTS 4&lt;br /&gt;
 Handle server_port = ...;&lt;br /&gt;
 s32 requesting_index;&lt;br /&gt;
 Handle handles[1 + MAX_CLIENTS] = { server_port };&lt;br /&gt;
 s32 connected_clients = 0;&lt;br /&gt;
 Handle reply_target = 0;&lt;br /&gt;
 &lt;br /&gt;
 while (true) {&lt;br /&gt;
     Result res = svcReplyAndReceive(&amp;amp;requesting_index, handles, 1 + connected_clients, reply_target);&lt;br /&gt;
 &lt;br /&gt;
     if (res == 0xC920181A) {&lt;br /&gt;
         // Session was closed by remote&lt;br /&gt;
         // TODO: Handle disconnects&lt;br /&gt;
         reply_target = 0;&lt;br /&gt;
         continue;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     if (requesting_index == 0) {&lt;br /&gt;
         // New connection in server_port&lt;br /&gt;
         ASSERT(connected_client &amp;lt; MAX_CLIENTS);&lt;br /&gt;
         svcAcceptSession(&amp;amp;handles[1 + connected_clients++], server_port);&lt;br /&gt;
         reply_target = 0;&lt;br /&gt;
         continue;&lt;br /&gt;
     }&lt;br /&gt;
 &lt;br /&gt;
     reply_target = handles[requesting_index];&lt;br /&gt;
 &lt;br /&gt;
     // Handle command here and write reply to command buffer&lt;br /&gt;
 }&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSPLCD:GetPowerState&amp;diff=23016</id>
		<title>GSPLCD:GetPowerState</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSPLCD:GetPowerState&amp;diff=23016"/>
		<updated>2025-01-11T20:24:31Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=Request=&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Header code [0x000E0040]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=Response=&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Header code&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Result code&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Power state (u8)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=Power state flags=&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Top backlight on&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Bottom backlight on&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| LC panel on&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Operation in progress&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Fatal error&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=Description=&lt;br /&gt;
Read LCD power state. If set, the fatal error bit halts any successive power operation; the result code tells the cause of the error.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Services&amp;diff=23015</id>
		<title>GSP Services</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Services&amp;diff=23015"/>
		<updated>2025-01-11T20:20:59Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
{{Anchor|GSPGPU}}{{Anchor|gsp::Gpu}}&lt;br /&gt;
= GSP service &amp;quot;gsp::Gpu&amp;quot; =&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Command Header&lt;br /&gt;
!  Available since system version&lt;br /&gt;
!  GSP rights required&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00010082&lt;br /&gt;
| &lt;br /&gt;
| Yes&lt;br /&gt;
| [[GSPGPU:WriteHWRegs|WriteHWRegs]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00020084&lt;br /&gt;
| &lt;br /&gt;
| Yes&lt;br /&gt;
| [[GSPGPU:WriteHWRegsWithMask|WriteHWRegsWithMask]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00030082&lt;br /&gt;
| &lt;br /&gt;
| Yes&lt;br /&gt;
| [[GSPGPU:WriteHWRegRepeat|WriteHWRegRepeat]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00040080&lt;br /&gt;
| &lt;br /&gt;
| Yes&lt;br /&gt;
| [[GSPGPU:ReadHWRegs|ReadHWRegs]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00050200&lt;br /&gt;
| &lt;br /&gt;
| Yes&lt;br /&gt;
| [[GSPGPU:SetBufferSwap|SetBufferSwap]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00060082&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| SetCommandList (Stubbed)&lt;br /&gt;
|-&lt;br /&gt;
| 0x000700C2&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| RequestDma (Stubbed)&lt;br /&gt;
|-&lt;br /&gt;
| 0x00080082&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:FlushDataCache|FlushDataCache]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00090082&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:InvalidateDataCache|InvalidateDataCache]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000A0044&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| RegisterInterruptEvents (Stubbed)&lt;br /&gt;
|-&lt;br /&gt;
| 0x000B0040&lt;br /&gt;
| &lt;br /&gt;
| Usually not (see below)&lt;br /&gt;
| [[GSPGPU:SetLcdForceBlack|SetLcdForceBlack]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000C0000&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000D0140&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:SetDisplayTransfer|SetDisplayTransfer]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000E0180&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:SetTextureCopy|SetTextureCopy]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000F0200&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:SetMemoryFill|SetMemoryFill]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00100040&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:SetAxiConfigQoSMode|SetAxiConfigQoSMode]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00110040&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:SetPerfLogMode|SetPerfLogMode]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00120000&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:GetPerfLog|GetPerfLog]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00130042&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00140000&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:UnregisterInterruptRelayQueue|UnregisterInterruptRelayQueue]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00150002&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:TryAcquireRight|TryAcquireRight]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00160042&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:AcquireRight|AcquireRight]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00170000&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:ReleaseRight|ReleaseRight]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00180000&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:ImportDisplayCaptureInfo|ImportDisplayCaptureInfo]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00190000&lt;br /&gt;
| &lt;br /&gt;
| See below&lt;br /&gt;
| [[GSPGPU:SaveVramSysArea|SaveVramSysArea]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x001A0000&lt;br /&gt;
| &lt;br /&gt;
| See below&lt;br /&gt;
| [[GSPGPU:RestoreVramSysArea|RestoreVramSysArea]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x001B0000&lt;br /&gt;
| &lt;br /&gt;
| Yes&lt;br /&gt;
| [[GSPGPU:ResetGpuCore|ResetGpuCore]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x001C0040&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:SetLedForceOff|SetLedForceOff]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x001D0040&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| SetTestCommand (Stubbed)&lt;br /&gt;
|-&lt;br /&gt;
| 0x001E0080&lt;br /&gt;
| &lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:SetInternalPriorities|SetInternalPriorities]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x001F0082&lt;br /&gt;
| [[8.0.0-18]]&lt;br /&gt;
| No&lt;br /&gt;
| [[GSPGPU:StoreDataCache|StoreDataCache]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The GSP module starts a thread for handling commands for each service session, a maximum of 4 processes can use this service at once. Official applications have an optional code-path which [[GSPGPU:WriteHWRegs|writes]] to registers during initialization, this is normally not used however.&lt;br /&gt;
&lt;br /&gt;
If a process has forcefully acquired rights (ErrDisp), attempting [[GSPGPU:SetLcdForceBlack|unset LCDs black-fill]] from another process will fail.&lt;br /&gt;
Saving/restoring VRAM requires bit0 of process [[GSPGPU:RegisterInterruptRelayQueue|flags]] to be set.&lt;br /&gt;
&lt;br /&gt;
{{Anchor|GSPLCD}}{{Anchor|gsp::Lcd}}&lt;br /&gt;
= GSP service &amp;quot;gsp::Lcd&amp;quot; =&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Command Header&lt;br /&gt;
!  Available since system version&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x00010040&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:EnableABL|EnableABL]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00020040&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:DisableABL|DisableABL]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00030080&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetRSLut|SetRSLut]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000400C0&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetRSParams|SetRSParams]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00050140&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetABLArea|SetABLArea]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00060140&lt;br /&gt;
| &lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x00070080&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetInertia|SetInertia]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000800C0&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetDitherMode|SetDitherMode]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00090140&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetDitherParams|SetDitherParams]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000A0080&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetBrightnessRaw|SetBrightnessRaw]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000B0080&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetBrightness|SetBrightness]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000C0040&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:ReloadConfig|ReloadConfig]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000D0040&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:RestoreConfig|RestoreConfig]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000E0000&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:GetPowerState|GetPowerState]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x000F0000&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:PowerOnAllBacklights|PowerOnAllBacklights]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00100000&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:PowerOffAllBacklights|PowerOffAllBacklights]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00110040&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:PowerOnBacklight|PowerOnBacklight]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00120040&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:PowerOffBacklight|PowerOffBacklight]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00130040&lt;br /&gt;
| &lt;br /&gt;
| [[GSPLCD:SetLedForceOff|SetLedForceOff]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x00140000&lt;br /&gt;
| [[8.0.0-18]]&lt;br /&gt;
| [[GSPLCD:GetVendor|GetVendor]] New3DS-only, stubbed on Old3DS: This only returns an error. Uninitialized data(not set by this command itself) is also written to u8 cmdreply_word[2].&lt;br /&gt;
|-&lt;br /&gt;
| 0x00150040&lt;br /&gt;
| [[8.0.0-18]]&lt;br /&gt;
| [[GSPLCD:GetBrightness|GetBrightness]] New3DS-only, stubbed on Old3DS: This only returns an error. Uninitialized data(not set by this command itself) is also written to u32 cmdreply_word[2].&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Unlike gsp::Gpu, GSP module does not start a separate thread for handling these service commands.&lt;br /&gt;
&lt;br /&gt;
= Version history =&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Version&lt;br /&gt;
!  Changes&lt;br /&gt;
|-&lt;br /&gt;
| [[8.0.0-18|v8196]]&lt;br /&gt;
| Support for the new LINEAR memory region was implemented(for cache commands and vaddr-&amp;gt;physaddr conversion). Support for the new process-mem 0x1E800000 region(however the GPU can&#039;t actually access this memory) was added for vaddr-&amp;gt;physaddr conversion. Originally GSP module ignored vaddr-&amp;gt;physaddr conversion errors(like with vaddrs outside of the handled ranges) and just wrote physaddr value0 to the GPU registers, however now GSP module returns an error for that instead(see [[GSP_Shared_Memory#Command_Buffer_Header|here]] regarding errors being written to GSP shared-mem). New services commands were added too, see above.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=New3DS=&lt;br /&gt;
There&#039;s separate GSP-module titles for Old3DS and New3DS. PTM CheckNew3DS is only used by the New3DS title, for copying that flag into a state field. Elsewhere that field is checked for running additional code only on New3DS, for processing various state / using [[QTM_Services|QTM]] commands.&lt;br /&gt;
&lt;br /&gt;
[[Category:Services]]&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSPGPU:SetMemoryFill&amp;diff=23014</id>
		<title>GSPGPU:SetMemoryFill</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSPGPU:SetMemoryFill&amp;diff=23014"/>
		<updated>2025-01-11T20:18:24Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Created page with &amp;quot;=Request= {{IPC/Request|Request}} {{IPC/RequestHeader|0x000F|8|0}} {{IPC/RequestEntry|First buffer physical start address}} {{IPC/RequestEntry|First buffer physical end addres...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=Request=&lt;br /&gt;
{{IPC/Request|Request}}&lt;br /&gt;
{{IPC/RequestHeader|0x000F|8|0}}&lt;br /&gt;
{{IPC/RequestEntry|First buffer physical start address}}&lt;br /&gt;
{{IPC/RequestEntry|First buffer physical end address}}&lt;br /&gt;
{{IPC/RequestEntry|First buffer fill value}}&lt;br /&gt;
{{IPC/RequestEntry|First buffer fill width}}&lt;br /&gt;
{{IPC/RequestEntry|Second buffer physical start address}}&lt;br /&gt;
{{IPC/RequestEntry|Second buffer physical end address}}&lt;br /&gt;
{{IPC/RequestEntry|Second buffer fill value}}&lt;br /&gt;
{{IPC/RequestEntry|Second buffer fill width}}&lt;br /&gt;
{{IPC/RequestEnd}}&lt;br /&gt;
&lt;br /&gt;
=Response=&lt;br /&gt;
{{IPC/Request|Response}}&lt;br /&gt;
{{IPC/RequestHeader|0x000F|1|0}}&lt;br /&gt;
{{IPC/RequestEntry|Result code}}&lt;br /&gt;
{{IPC/RequestEnd}}&lt;br /&gt;
&lt;br /&gt;
=Description=&lt;br /&gt;
Stubbed, replaced by the [[GSP_Shared_Memory#Trigger_Memory_Fill|MemoryFill]] command request.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSPGPU:SetDisplayTransfer&amp;diff=23013</id>
		<title>GSPGPU:SetDisplayTransfer</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSPGPU:SetDisplayTransfer&amp;diff=23013"/>
		<updated>2025-01-11T20:17:09Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Created page with &amp;quot;=Request= {{IPC/Request|Request}} {{IPC/RequestHeader|0x000D|5|0}} {{IPC/RequestEntry|Physical source address}} {{IPC/RequestEntry|Source parameters (bit0-15: width, bit 16-31...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=Request=&lt;br /&gt;
{{IPC/Request|Request}}&lt;br /&gt;
{{IPC/RequestHeader|0x000D|5|0}}&lt;br /&gt;
{{IPC/RequestEntry|Physical source address}}&lt;br /&gt;
{{IPC/RequestEntry|Source parameters (bit0-15: width, bit 16-31: height)}}&lt;br /&gt;
{{IPC/RequestEntry|Physical destination address}}&lt;br /&gt;
{{IPC/RequestEntry|Destination parameters (bit0-15: width, bit 16-31: height)}}&lt;br /&gt;
{{IPC/RequestEntry|[[GPU/External_Registers#Flags_Register_-_0x1EF00C10|Flags]]}}&lt;br /&gt;
{{IPC/RequestEnd}}&lt;br /&gt;
&lt;br /&gt;
=Response=&lt;br /&gt;
{{IPC/Request|Response}}&lt;br /&gt;
{{IPC/RequestHeader|0x000D|1|0}}&lt;br /&gt;
{{IPC/RequestEntry|Result code}}&lt;br /&gt;
{{IPC/RequestEnd}}&lt;br /&gt;
&lt;br /&gt;
=Description=&lt;br /&gt;
Stubbed, replaced by the [[GSP_Shared_Memory#Trigger_Display_Transfer|DisplayTransfer]] command request.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSPGPU:SetTextureCopy&amp;diff=23012</id>
		<title>GSPGPU:SetTextureCopy</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSPGPU:SetTextureCopy&amp;diff=23012"/>
		<updated>2025-01-11T20:16:11Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Created page with &amp;quot;=Request= {{IPC/Request|Request}} {{IPC/RequestHeader|0x000E|6|0}} {{IPC/RequestEntry|Physical source address}} {{IPC/RequestEntry|Physical destination address}} {{IPC/Request...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=Request=&lt;br /&gt;
{{IPC/Request|Request}}&lt;br /&gt;
{{IPC/RequestHeader|0x000E|6|0}}&lt;br /&gt;
{{IPC/RequestEntry|Physical source address}}&lt;br /&gt;
{{IPC/RequestEntry|Physical destination address}}&lt;br /&gt;
{{IPC/RequestEntry|Size}}&lt;br /&gt;
{{IPC/RequestEntry|Source parameters (bit0-15: line width, bit 16-31: gap)}}&lt;br /&gt;
{{IPC/RequestEntry|Destination parameters (bit0-15: line width, bit 16-31: gap)}}&lt;br /&gt;
{{IPC/RequestEntry|Flags (bit2: set when using gaps, bit3: always set)}}&lt;br /&gt;
{{IPC/RequestEnd}}&lt;br /&gt;
&lt;br /&gt;
=Response=&lt;br /&gt;
{{IPC/Request|Response}}&lt;br /&gt;
{{IPC/RequestHeader|0x000E|1|0}}&lt;br /&gt;
{{IPC/RequestEntry|Result code}}&lt;br /&gt;
{{IPC/RequestEnd}}&lt;br /&gt;
&lt;br /&gt;
=Description=&lt;br /&gt;
Stubbed, replaced by the [[GSP_Shared_Memory#Trigger_Texture_Copy|TextureCopy]] command request.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=22945</id>
		<title>GPU/External Registers</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GPU/External_Registers&amp;diff=22945"/>
		<updated>2024-12-16T22:47:37Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Reword TextureCopy&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the address range accessible from the ARM11, used to configure the basic GPU functionality. For information about the internal registers used for 3D rendering, see [[GPU/Internal Registers]].&lt;br /&gt;
&lt;br /&gt;
== Map ==&lt;br /&gt;
Address mappings for the external registers. GSPGPU:WriteHWRegs takes these addresses relative to 0x1EB00000. &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! User VA&lt;br /&gt;
! PA&lt;br /&gt;
! Length&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00000&lt;br /&gt;
| 0x10400000&lt;br /&gt;
| 4&lt;br /&gt;
| Hardware ID&lt;br /&gt;
| Bit2: new model&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00004&lt;br /&gt;
| 0x10400004&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00010&lt;br /&gt;
| 0x10400010&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill1]] &amp;quot;PSC0&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00020&lt;br /&gt;
| 0x10400020&lt;br /&gt;
| 16&lt;br /&gt;
| [[#Memory Fill|Memory Fill2]] &amp;quot;PSC1&amp;quot;&lt;br /&gt;
| GX command 2&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00030&lt;br /&gt;
| 0x10400030&lt;br /&gt;
| 4&lt;br /&gt;
| VRAM bank control&lt;br /&gt;
| Bits 8-11 = bank[i] disabled; other bits are unused.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00034&lt;br /&gt;
| 0x10400034&lt;br /&gt;
| 4&lt;br /&gt;
| GPU Busy&lt;br /&gt;
| Bit26 = PSC0, bit27 = PSC1, Bit30 = PPF, Bit31 = P3D&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00050&lt;br /&gt;
| 0x10400050&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x22221200 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00054&lt;br /&gt;
| 0x10400054&lt;br /&gt;
| 4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFF2 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000C0&lt;br /&gt;
| 0x104000C0&lt;br /&gt;
| 4&lt;br /&gt;
| Backlight control&lt;br /&gt;
| Writes 0x0 to allow backlights to turn off, 0x20000000 to force them always on.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00400&lt;br /&gt;
| 0x10400400&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC0&amp;quot; (top screen)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00500&lt;br /&gt;
| 0x10400500&lt;br /&gt;
| 0x100&lt;br /&gt;
| [[#LCD Source Framebuffer Setup|Framebuffer Setup]] &amp;quot;PDC1&amp;quot; (bottom)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| 0x10400C00&lt;br /&gt;
| ?&lt;br /&gt;
| [[#Transfer_Engine|Transfer Engine]] &amp;quot;DMA&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|colspan=&amp;quot;5&amp;quot;| 0x1EF01000/0x10401000 - 0x1EF01C00/0x10401C00 maps to [[GPU/Internal_Registers|GPU internal registers]]. These registers are usually not read/written directly here, but are written using the command list interface below (corresponding to the GPUREG_CMDBUF_* internal registers)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01000&lt;br /&gt;
| 0x10401000&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0 on GPU init and before the Command List is used&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF01080&lt;br /&gt;
| 0x10401080&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0x12345678 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010C0&lt;br /&gt;
| 0x104010C0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 0xFFFFFFF0 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF010D0&lt;br /&gt;
| 0x104010D0&lt;br /&gt;
| 0x4&lt;br /&gt;
| ?&lt;br /&gt;
| Writes 1 on GPU init.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF014??&lt;br /&gt;
| 0x104014??&lt;br /&gt;
| 0x14&lt;br /&gt;
| &amp;quot;PPF&amp;quot; ?&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| 0x104018E0&lt;br /&gt;
| 0x14&lt;br /&gt;
| [[#Command_List|Command List]] &amp;quot;P3D&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Memory Fill ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  User VA&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X0&lt;br /&gt;
| Buffer start physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X4&lt;br /&gt;
| Buffer end physaddr &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000X8&lt;br /&gt;
| Fill value&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF000XC&lt;br /&gt;
| Control. bit0: start/busy, bit1: finished, bit8-9: fill-width (0=16bit, 1=3=24bit, 2=32bit)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Memory fills are used to initialize buffers in memory with a given value, similar to memset. A memory fill is triggered by setting bit0 in the control register. Doing so aborts any running memory fills on that filling unit. Upon completion, the hardware unsets bit0 and sets bit1 and fires interrupt PSC0.&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP Shared Memory#GX SetMemoryFill|GX SetMemoryFill]].&lt;br /&gt;
&lt;br /&gt;
== LCD Source Framebuffer Setup ==&lt;br /&gt;
&lt;br /&gt;
All of these registers must be accessed with 32bit operations regardless of the registers&#039; actual bit size.&lt;br /&gt;
&lt;br /&gt;
The naming of these parameters reflects the physical characteristics of the displays, and not the way the 3DS is normally held.&lt;br /&gt;
&lt;br /&gt;
To make sense of these values, the 3DS must be held in a way, so that the bottom screen is in the left hand, and the top screen is in the right hand, and that way the first pixel will be in the top-left corner, as it should be. If the 3DS is held normally, the first pixel is in the bottom-left corner.&lt;br /&gt;
&lt;br /&gt;
All pixel and scanline timing values are 12bits, unless noted. This also applies to those fields where two u16 are combined into one register. Each u16 field is only 12bits in size. timin&lt;br /&gt;
&lt;br /&gt;
The horizontal timing parameter order is as follows (values may overflow through HTotal register value):&lt;br /&gt;
 0x10 &amp;lt; 0x14 &amp;lt;= 0x60.LO &amp;lt;= 0x04 &amp;lt;= 0x60.HI &amp;lt;= 0x08 &amp;lt;= 0x0C &amp;lt;= 0x10&lt;br /&gt;
 0x18 &amp;lt;= 0x60.LO&lt;br /&gt;
&lt;br /&gt;
Timing starts from HCount == 0, then each absolute value in the beforementioned register chain triggers when HCount == register, latching the primitive display controller into a new mode.&lt;br /&gt;
There is an inherent latch order, where if two simultenaous events occur, one event wins over another.&lt;br /&gt;
&lt;br /&gt;
 Known latched modes (in order):&lt;br /&gt;
 - HSync (triggers a line to the LCD to move to the next line)&lt;br /&gt;
 - Back porch (area between HSync and border being displayed, no pixels pushed, min 16 pixel clocks, otherwise the screen gets glitchy)&lt;br /&gt;
 - Left border start (no image data is being displayed, just a configurable solid color)&lt;br /&gt;
 - Image start (pixel data is being DMA&#039;d from video memory or main RAM)&lt;br /&gt;
 - Right border start/Image end (border color is being displayed after the main image)&lt;br /&gt;
 - Unknown synchronization (supposed to be probably right border end, but this mode seems to be broken or not do anything)&lt;br /&gt;
 - Front porch (no pixels pushed, 68 clock min, otherwise the screen doesn&#039;t sync properly, and really glitches out)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
! Offset&lt;br /&gt;
! Name&lt;br /&gt;
! Comments&lt;br /&gt;
|-&lt;br /&gt;
| 0x00&lt;br /&gt;
| HTotal&lt;br /&gt;
| The total width of a timing scanline. In other words, this is the horizontal refresh clock divider value.&lt;br /&gt;
&lt;br /&gt;
HClock = PClock / (HTotal + 1)&lt;br /&gt;
|-&lt;br /&gt;
| 0x04&lt;br /&gt;
| HStart&lt;br /&gt;
| Determines when the image is going to be displayed in the visible region (register 0x60).&lt;br /&gt;
|-&lt;br /&gt;
| 0x08&lt;br /&gt;
| HBR&lt;br /&gt;
| Right border start(?). Does nothing.&lt;br /&gt;
&lt;br /&gt;
While this register seems to have no impact on the image whatsoever, it still has to be set to a valid value.&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| 0x0C&lt;br /&gt;
| HPF&lt;br /&gt;
| Front porch. The image is blanked during this period, and no pixels are pushed to the LCD.&lt;br /&gt;
&lt;br /&gt;
Unknown why, but a single dot of red is displayed before entering this mode.&lt;br /&gt;
|-&lt;br /&gt;
| 0x10&lt;br /&gt;
| HSync&lt;br /&gt;
| Triggers a HSync pulse.&lt;br /&gt;
&lt;br /&gt;
Based on behavior, this needs to last at least a pixel clock for the LCD to register the sync.&lt;br /&gt;
|-&lt;br /&gt;
| 0x14&lt;br /&gt;
| HPB&lt;br /&gt;
| Back porch? Has to be at least one bigger than HSync, otherwise HSync never triggers.&lt;br /&gt;
&lt;br /&gt;
The display is blank, and the LCD displays nothing in this period (doesn&#039;t push pixels).&lt;br /&gt;
|-&lt;br /&gt;
| 0x18&lt;br /&gt;
| HBL&lt;br /&gt;
| Left border trigger treshold. Enables pushing pixels to the display.&lt;br /&gt;
&lt;br /&gt;
If this value is smaller than the back porch, then the back porch period will be zero, and the border will be immediately displayed upon entering the back porch period.&lt;br /&gt;
&lt;br /&gt;
Can be lower than HSync, as the back porch is what takes the controller out of HSync.&lt;br /&gt;
&lt;br /&gt;
Must be &amp;lt;= HDisp start (reg 0x60 low u16), otherwise no pixels will be pushed due to a glitched state.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1C&lt;br /&gt;
| H Interrupt timing&lt;br /&gt;
| Made up from two u16 values, PDC interrupt line is asserted when HCount == low u16, and most likely deasserted when HCount == high u16.&lt;br /&gt;
&lt;br /&gt;
There seems to be some limitations though:&lt;br /&gt;
* low u16 must be smaller than high u16&lt;br /&gt;
* if low u16 is less than HTotal then high u16 must also be smaller than HTotal&lt;br /&gt;
* setting low u16 to &amp;gt;= HTotal disables the interrupt ever firing&lt;br /&gt;
&lt;br /&gt;
This is configured by gsp in a way so that low u16 equals to HTotal, meaning the HSync interrupt will never fire.&lt;br /&gt;
|-&lt;br /&gt;
| 0x20&lt;br /&gt;
| low u16: ???&lt;br /&gt;
high u16: ???&lt;br /&gt;
| ???&lt;br /&gt;
|-&lt;br /&gt;
| 0x24&lt;br /&gt;
| VTotal&lt;br /&gt;
| Total height of the timing window. Can be interpreted as the vertical clock divider.&lt;br /&gt;
&lt;br /&gt;
VClock = PClock / (HTotal + 1) / (VTotal + 1)&lt;br /&gt;
&lt;br /&gt;
Setting this to 494 lowers framerate to about 50.040660858 Hz ((268111856 / 24) / (450 + 1) / (494 + 1)).&lt;br /&gt;
|-&lt;br /&gt;
| 0x28&lt;br /&gt;
| ?&lt;br /&gt;
| Seems to determine the vertical blanking interval.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Setting this to lower than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will cut off the top &amp;lt;code&amp;gt;VTotal - VDisp - thisvalue&amp;lt;/code&amp;gt; lines.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;VTotal - VDisp&amp;lt;/code&amp;gt; will make the image be pushed downwards with the overscan color visible.&lt;br /&gt;
&lt;br /&gt;
Setting this to higher than &amp;lt;code&amp;gt;HTotal&amp;lt;/code&amp;gt; will make the GPU skip vertical pixel data synchronization (hence filling the screen with the rest of the pixel data past the given screen framebuffer size). Also will skip &amp;lt;code&amp;gt;thisvalue + somevalue - HTotal&amp;lt;/code&amp;gt; lines into the &amp;quot;global&amp;quot; pixel buffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x30&lt;br /&gt;
| ?&lt;br /&gt;
| Total amount of vertical scanlines in the pixel buffer, must be bigger than *an unknown blanking-like value*. If this value is less than VDisp then the last two scanlines will be repeated interlaced until VDisp is reached.&lt;br /&gt;
|-&lt;br /&gt;
| 0x34&lt;br /&gt;
| VDisp(?)&lt;br /&gt;
| Total amonut of vertical scanlines displayed (only for top screen it seems like). If this value is less than VTotal then the rest of the scanlines will not be updated on the screen, so those will slowly fade out. Must be bigger than *an unknown blanking-like value*, otherwise an underflow will happen.&lt;br /&gt;
|-&lt;br /&gt;
| 0x38&lt;br /&gt;
| Vertical data offset(?)&lt;br /&gt;
| ??? Seems to offset the screen upwards if this value is high enough. If this value is higher or equal to *some value* (aka. if less than one scanline is displayed on the screen) then the screen will lose synchronization.&lt;br /&gt;
|-&lt;br /&gt;
| 0x40&lt;br /&gt;
| V Interrupt timing&lt;br /&gt;
| Similar to H Interrupt timing (0x1C), except the comparison is done against VCount, the limitations are emposed on VTotal, and the interrupt that fires is VSync.&lt;br /&gt;
&lt;br /&gt;
One important note is that it seems like the VSync interrupt always fires at HCount == 0, and there doesn&#039;t seem to be a register to control this behavior.&lt;br /&gt;
|-&lt;br /&gt;
| 0x44&lt;br /&gt;
| ???&lt;br /&gt;
| similar functionality to 0x10&lt;br /&gt;
|-&lt;br /&gt;
| 0x48&lt;br /&gt;
| ???&lt;br /&gt;
| bit0 seems to disable HSync, bit8 seems to disable VSync, rest of the bits aren&#039;t writable.&lt;br /&gt;
|-&lt;br /&gt;
| 0x4C&lt;br /&gt;
| Overscan filler color&lt;br /&gt;
| 24bits(? top 8bits ignored)&lt;br /&gt;
&lt;br /&gt;
When the visible region is being drawn, but the timing parameters are set up in a way that the framebuffer is smaller than the visible region, it will be filled by this color.&lt;br /&gt;
|-&lt;br /&gt;
| 0x50&lt;br /&gt;
| HCount&lt;br /&gt;
| Horizontal &amp;quot;beam position&amp;quot; counter. Note that this value does not equal to the current pixel being drawn.&lt;br /&gt;
|-&lt;br /&gt;
| 0x54&lt;br /&gt;
| VCount&lt;br /&gt;
| Vertical &amp;quot;beam position&amp;quot; counter. Note that the scanline being drawn isn&#039;t equal to this value.&lt;br /&gt;
|-&lt;br /&gt;
| 0x5C&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: Image width (including some offset?)&lt;br /&gt;
high u16: Image height??? (seems to be unused)&lt;br /&gt;
|-&lt;br /&gt;
| 0x60&lt;br /&gt;
| HDisp&lt;br /&gt;
| low u16: Image start (border --&amp;gt; pixel data)&lt;br /&gt;
high u16: Image end (pixel data --&amp;gt; border)&lt;br /&gt;
|-&lt;br /&gt;
| 0x64&lt;br /&gt;
| ???&lt;br /&gt;
| low u16: unknown&lt;br /&gt;
high u16: framebuffer total height (amount of scanlines blitted regardless of framebuffer height)&lt;br /&gt;
|-&lt;br /&gt;
| 0x68&lt;br /&gt;
| Framebuffer A first address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x6C&lt;br /&gt;
| Framebuffer A second address&lt;br /&gt;
| For top screen, this is the left eye 3D framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x70&lt;br /&gt;
| Framebuffer format and other settings&lt;br /&gt;
| See [[#Framebuffer_format|framebuffer format]]&lt;br /&gt;
|-&lt;br /&gt;
| 0x74&lt;br /&gt;
| PDC control&lt;br /&gt;
| Bit 0: Enable display controller.&lt;br /&gt;
Bit 8: HBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 9: VBlank IRQ mask (0 = enabled).&lt;br /&gt;
Bit 10: Error IRQ mask? (0 = enabled).&lt;br /&gt;
Bit 16: Output enable?&lt;br /&gt;
|-&lt;br /&gt;
| 0x78&lt;br /&gt;
| Framebuffer select and status&lt;br /&gt;
| Bit 0: Next framebuffer to display (after VBlank).&lt;br /&gt;
Bit 4: Currently displaying framebuffer?&lt;br /&gt;
Bit 8: Reset FIFO?&lt;br /&gt;
Bit 16: HBlank IRQ status/ack. Write 1 to aknowledge.&lt;br /&gt;
Bit 17: VBlank IRQ status/ack.&lt;br /&gt;
Bit 18: Error IRQ status/ack?&lt;br /&gt;
|-&lt;br /&gt;
| 0x80&lt;br /&gt;
| Color lookup table index select&lt;br /&gt;
| 8bits, write-only&lt;br /&gt;
|-&lt;br /&gt;
| 0x84&lt;br /&gt;
| Color lookup table indexed element&lt;br /&gt;
| Contains the value of the color lookup table indexed by the above register, 24bits, RGB8 (0x00BBGGRR)  &lt;br /&gt;
Accessing this register will increase the index register by one&lt;br /&gt;
|-&lt;br /&gt;
| 0x90&lt;br /&gt;
| Framebuffer stride&lt;br /&gt;
| 32bits (bottom 3bits ignored?)&lt;br /&gt;
&lt;br /&gt;
Distance in bytes between the start of two framebuffer rows (must be a multiple of 8).&lt;br /&gt;
&lt;br /&gt;
In other words, this can be interpreted as the amount to add to the framebuffer pointer after displaying a scanline.&lt;br /&gt;
&lt;br /&gt;
Setting this to zero will cause only the first line of the image to be displayed repeated on the entire display. With the HSync interrupt it&#039;s possible to &amp;quot;race the beam&amp;quot; to (ab)use this feature.&lt;br /&gt;
&lt;br /&gt;
Because of this simplicity, writing a negative value here VFlips the image, although that requires the framebuffer pointer register to be set to the start of the last scanline, instead of at the start of the framebuffer.&lt;br /&gt;
|-&lt;br /&gt;
| 0x94&lt;br /&gt;
| Framebuffer B first address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|-&lt;br /&gt;
| 0x98&lt;br /&gt;
| Framebuffer B second address&lt;br /&gt;
| For top screen, this is the right eye 3D framebuffer. Unused for bottom screen in userland.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer format ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 2-0&lt;br /&gt;
| [[#Framebuffer_color_formats|Color format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5-4&lt;br /&gt;
| Framebuffer interlacing mode&lt;br /&gt;
&lt;br /&gt;
 0 - A  (no interlacing)&lt;br /&gt;
 1 - AA (scanline doubling)&lt;br /&gt;
 2 - AB (interlace enable)&lt;br /&gt;
 3 - BA (same as above, but the fields are inverted)&lt;br /&gt;
&lt;br /&gt;
In AB and BA interlace modes, a scanline from each framebuffer is output in an alternating manner. In AB mode, Framebuffer A is output on the frist display scanline. Similarly, in BA mode, Framebuffer B gets output to the first display scanline.&lt;br /&gt;
&lt;br /&gt;
The way AB and BA modes work, is that a scanline is output, the framebuffer stride value is added to the internal scanline pointer value, and the other framebuffer is selected. And this alternates until the end of the draw region.&lt;br /&gt;
&lt;br /&gt;
AA interlacing works like AB interlacing, except both internal framebuffer pointers are set to the Framebuffer A pointer value.&lt;br /&gt;
&lt;br /&gt;
In A mode (no interlacing), it doesn&#039;t switch to the other framebuffer at the end of outpuitting a scanline to the display.&lt;br /&gt;
&lt;br /&gt;
Bottom screen has this set to 0 (A mode, no interlacing) at all times.  &lt;br /&gt;
Top screen uses AB interlacing in 3D mode (with 3D slider enabled), and A mode (no interlacing) in 2D mode.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Alternative pixel output mode*&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 9-8&lt;br /&gt;
| DMA size&lt;br /&gt;
&lt;br /&gt;
 0 -  4 FCRAM words (32 bytes)&lt;br /&gt;
 1 -  8 FCRAM words (64 bytes)&lt;br /&gt;
 2 - 16 FCRAM words (128 bytes)&lt;br /&gt;
 3 - ???&lt;br /&gt;
&lt;br /&gt;
FCRAM doesn&#039;t support DMA size 3, as it can only burst up to 16 words (128 bytes), and will show a black screen instead.&lt;br /&gt;
|-&lt;br /&gt;
| 31-16&lt;br /&gt;
| Unknown&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;*&amp;lt;/nowiki&amp;gt; The weird thing about bit6, is that it works different between the bottom and top LCD. On the bottom LCD, it doubles the number of outputted pixels (so the same pixel is outputted twice, effectively doing pixel/column doubling). However on the top screen, it does scanline doubling instead.  &lt;br /&gt;
Most likely the top screen receives two pixels at once per clock unit, outputting two scanlines simultaneously.&lt;br /&gt;
&lt;br /&gt;
On a 2DS, it seems to have no effect on the top part of the display, and on the bottom screen it just shifts the framebuffer to the right two pixels.&lt;br /&gt;
&lt;br /&gt;
GSP module only allows the LCD stereoscopy (3D) to be enabled when bit5=1 and bit6=0 here. When GSP module updates this register, GSP module will automatically disable the stereoscopy if those bits are not set for enabling stereoscopy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When both interlacing and alternative mode is disabled (bit6=0), the full resolution of the top screen (240x800) can be utilized if the PDC registers are updated to accomodate this higher resolution. GSP contains tables for this mode (gsp mode == 1). GSP automatically applies this mode if both bit5 and bit6 are cleared. This is also the default, and the only valid mode for the bottom screen in userland.&lt;br /&gt;
&lt;br /&gt;
If only AB interlacing is enabled (bit5=1, bit6=0), gsp detects this as a request to switch to 3D mode (gsp mode == 2), and enables the parallax barrier.  &lt;br /&gt;
It&#039;s unknown how to control this, but some other PDC registers control if interlacing should be done by true interleaving (both framebuffers are treated as 240x400), or by skipping lines (both framebuffers are treated as 240x800).&lt;br /&gt;
&lt;br /&gt;
If only alternative mode is enabled (bit5=0, bit6=1), gsp detects it as a request to switch back to 2D mode for the top screen (gsp mode == 0). This is also the default mode for the top screen.&lt;br /&gt;
&lt;br /&gt;
Both interlacing and scan doubling can&#039;t be enabled in usermode, but it works as expected in baremetal.&lt;br /&gt;
&lt;br /&gt;
=== Framebuffer color formats ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Value&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| GL_RGBA8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| GL_RGB8_OES&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| GL_RGB565_OES&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| GL_RGB5_A1_OES&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| GL_RGBA4_OES&lt;br /&gt;
|}&lt;br /&gt;
Color components are laid out in reverse byte order, with the most significant bits used first (i.e. non-24-bit pixels are stored as a little-endian values). For instance, a raw data stream of two GL_RGB565_OES pixels looks like GGGBBBBB RRRRRGGG GGGBBBBB RRRRRGGG.&lt;br /&gt;
&lt;br /&gt;
Color formats 5, 6, and 7 are blocked by gsp, but they behave as pixel-doubled RGBA8 (not line doubling, but instead the same pixel is output twice) if used outside of userland.&lt;br /&gt;
&lt;br /&gt;
== Transfer Engine ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C00&lt;br /&gt;
| Input physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C04&lt;br /&gt;
| Output physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C08&lt;br /&gt;
| DisplayTransfer output width (bits 0-15) and height (bits 16-31).&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C0C&lt;br /&gt;
| DisplayTransfer input width and height.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C10&lt;br /&gt;
| Transfer flags. (See below)&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C14&lt;br /&gt;
| GSP module writes value 0 here prior to writing to 0x1EF00C18, for cmd3.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C18&lt;br /&gt;
|  Setting bit0 starts the transfer. Upon completion, bit0 is unset and bit8 is set.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C1C&lt;br /&gt;
|  ?&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C20&lt;br /&gt;
| TextureCopy total amount of data to copy, in bytes.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C24&lt;br /&gt;
| TextureCopy input line width (bits 0-15) and gap (bits 16-31), in 16 byte units.&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF00C28&lt;br /&gt;
| TextureCopy output line width and gap.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These registers are used by [[GSP_Shared_Memory|GX command]] 3 and 4. For cmd4, *0x1EF00C18 |= 1 is used instead of just writing value 1. The DisplayTransfer registers are only used if bit 3 of the flags is unset and ignored otherwise. The TextureCopy registers are likewise only used if bit 3 is set, and ignored otherwise.&lt;br /&gt;
&lt;br /&gt;
==== Flags Register - 0x1EF00C10 ====&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Bit&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| When set, the framebuffer data is flipped vertically.&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| When set, the input framebuffer is treated as linear and converted to tiled in the output, converts tiled-&amp;gt;linear when unset.&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| This bit is required when the output width is less than the input width for the hardware to properly crop the lines, otherwise the output will be mis-aligned.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Uses a TextureCopy mode transfer. See below for details.&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Don&#039;t perform tiled-linear conversion. Incompatible with bit 1, so only tiled-tiled transfers can be done, not linear-linear.&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 10-8&lt;br /&gt;
| Input framebuffer color format, value0 and value1 are the same as the [[GPU Registers#Framebuffer_color_formats|LCD Source Framebuffer Formats]] (usually zero)&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 14-12&lt;br /&gt;
| Output framebuffer color format&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| Use 32x32 block tiling mode, instead of the usual 8x8 one. Output dimensions must be multiples of 32, even if cropping with bit 2 set above.&lt;br /&gt;
|-&lt;br /&gt;
| 17-23&lt;br /&gt;
| Not writable&lt;br /&gt;
|-&lt;br /&gt;
| 24-25&lt;br /&gt;
| Scale down the input image using a box filter. 0 = No downscale, 1 = 2x1 downscale. 2 = 2x2 downscale, 3 = invalid&lt;br /&gt;
|-&lt;br /&gt;
| 31-26&lt;br /&gt;
| Not writable&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== TextureCopy ===&lt;br /&gt;
&lt;br /&gt;
When bit 3 of the control register is set, the hardware performs a TextureCopy-mode transfer: no format conversions are done, instead a raw data copy is performed from the source to the destination, with a configurable gap between lines. All bits of the control register are ignored, except for input/output dimensions, which are used for line width and gap, and bit 2, which must be set when gaps are used.&lt;br /&gt;
&lt;br /&gt;
The total amount of bytes to copy is specified in the size register, the hardware loops reading lines from the input and writing them to the output until this amount is copied. The gap specifies the number of bytes to skip after each line read (a gap of 0 results in a contiguous read). Gaps do not count towards the total size of the transfer.&lt;br /&gt;
&lt;br /&gt;
When setting line width and gap they must be divided by 2 (it can be thought as the calculation being done in bits, and the values being stripped of their lower 4 bits for the alignment). For example, if the left half of a 32x32 RGB8 texture is to be copied, the parameters will be:&lt;br /&gt;
 line width = (16 * 24) &amp;gt;&amp;gt; 4 = 24&lt;br /&gt;
 gap = line width&lt;br /&gt;
 size = (16 * 32 * 24) &amp;gt;&amp;gt; 4 = 768&lt;br /&gt;
&lt;br /&gt;
By correctly calculating the input and output gap sizes it is possible to use this functionality to copy arbitrary sub-rectangles between differently-sized framebuffers or textures, which is one of its main uses over a regular no-conversion DisplayTransfer. When copying tiled textures/framebuffers it&#039;s important to remember that the contents of a tile are laid out sequentially in memory, and so this should be taken into account when calculating the transfer parameters.&lt;br /&gt;
&lt;br /&gt;
Specifying invalid/junk values for the TextureCopy dimensions can result in the GPU hanging while attempting to process this TextureCopy. For instance, when in contiguous mode the size must be at least 16; when in gap mode, the size must be at least 192, and the line width must not be 0.&lt;br /&gt;
&lt;br /&gt;
== Command List ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
!  Register address&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E0&lt;br /&gt;
| Buffer size in bytes &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018E8&lt;br /&gt;
| Buffer physical address &amp;gt;&amp;gt; 3&lt;br /&gt;
|-&lt;br /&gt;
| 0x1EF018F0&lt;br /&gt;
| Setting bit0 to 1 enables processing GPU command execution. Upon completion, bit0 seems to be reset to 0.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These 3 registers are used by [[GSP_Shared_Memory|GX command]] 1. This is used for [[GPU/Internal_Registers|GPU commands]].&lt;br /&gt;
&lt;br /&gt;
== Framebuffers ==&lt;br /&gt;
These LCD framebuffers normally contain the last rendered frames from the GPU. The framebuffers are drawn from left-to-right, instead of top-to-bottom.(Thus the beginning of the framebuffer is drawn starting at the left side of the screen)&lt;br /&gt;
&lt;br /&gt;
Both of the 3D screen left/right framebuffers are displayed regardless of the 3D slider&#039;s state, however when the 3D slider is set to &amp;quot;off&amp;quot; the 3D effect is disabled. Normally when the 3D slider&#039;s state is set to &amp;quot;off&amp;quot; the left/right framebuffer addresses are set to the same physical address. When the 3D effect is disabled and the left/right framebuffers are set to separate addresses, the LCD seems to alternate between displaying the left/right framebuffer each frame.&lt;br /&gt;
&lt;br /&gt;
==== Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00400 = 0x1C2&lt;br /&gt;
* 0x1EF00404 = 0xD1&lt;br /&gt;
* 0x1EF00408 = 0x1C1&lt;br /&gt;
* 0x1EF0040C = 0x1C1&lt;br /&gt;
* 0x1EF00410 = 0&lt;br /&gt;
* 0x1EF00414 = 0xCF&lt;br /&gt;
* 0x1EF00418 = 0xD1&lt;br /&gt;
* 0x1EF0041C = 0x1C501C1&lt;br /&gt;
* 0x1EF00420 = 0x10000&lt;br /&gt;
* 0x1EF00424 = 0x19D&lt;br /&gt;
* 0x1EF00428 = 2&lt;br /&gt;
* 0x1EF0042C = 0x1C2&lt;br /&gt;
* 0x1EF00430 = 0x1C2&lt;br /&gt;
* 0x1EF00434 = 0x1C2&lt;br /&gt;
* 0x1EF00438 = 1&lt;br /&gt;
* 0x1EF0043C = 2&lt;br /&gt;
* 0x1EF00440 = 0x1960192&lt;br /&gt;
* 0x1EF00444 = 0&lt;br /&gt;
* 0x1EF00448 = 0&lt;br /&gt;
* 0x1EF0045C = 0x19000F0&lt;br /&gt;
* 0x1EF00460 = 0x1c100d1&lt;br /&gt;
* 0x1EF00464 = 0x1920002&lt;br /&gt;
* 0x1EF00470 = 0x80340&lt;br /&gt;
* 0x1EF0049C = 0&lt;br /&gt;
&lt;br /&gt;
==== More Init Values from nngxInitialize for Top Screen ====&lt;br /&gt;
* 0x1EF00468 = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF0046C = 0x18300000, later changed by GSP module when updating state, framebuffer&lt;br /&gt;
* 0x1EF00494 = 0x18300000&lt;br /&gt;
* 0x1EF00498 = 0x18300000&lt;br /&gt;
* 0x1EF00478 = 1, doesn&#039;t stay 1, read as 0&lt;br /&gt;
* 0x1EF00474 = 0x10501&lt;br /&gt;
&lt;br /&gt;
[[Category:GPU]]&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=22927</id>
		<title>GSP Shared Memory</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSP_Shared_Memory&amp;diff=22927"/>
		<updated>2024-12-16T00:07:42Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Document PPF behaviour&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page describes the structure of the GSP [[GSPGPU:RegisterInterruptRelayQueue|shared]] memory. Interrupt, framebuffer, and GX command data is stored here.&lt;br /&gt;
&lt;br /&gt;
=Interrupt Queue=&lt;br /&gt;
&lt;br /&gt;
The Interrupt queue is located at sharedMemBase + (clientID * 0x40).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0x0&lt;br /&gt;
| Offset from the count where to save incoming interrupts&lt;br /&gt;
|-&lt;br /&gt;
| 0x1&lt;br /&gt;
| Count (max 0x20 for PDC, 0x34 for others)&lt;br /&gt;
|-&lt;br /&gt;
| 0x2&lt;br /&gt;
| Missed other interrupts (set to 1 when 0 and count &amp;gt;= 0x34)&lt;br /&gt;
|-&lt;br /&gt;
| 0x3&lt;br /&gt;
| Flags (bit0 = skip PDC)&lt;br /&gt;
|-&lt;br /&gt;
| 0x4-0x7&lt;br /&gt;
| Missed PDC0 (incremented when flags.bit0 is clear and count &amp;gt;= 0x20) &lt;br /&gt;
|-&lt;br /&gt;
| 0x8-0xB&lt;br /&gt;
| Missed PDC1 (same as above)&lt;br /&gt;
|-&lt;br /&gt;
| 0xC-0x3F&lt;br /&gt;
| Interrupt list (u8) (0=PSC0, 1=PSC1, 2=PDC0/VBlankTop, 3=PDC1/VBlankBottom, 4=PPF, 5=P3D, 6=DMA)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
GSP fills the interrupt list, then triggers the event set with [[GSPGPU:RegisterInterruptRelayQueue|RegisterInterruptRelayQueue]] for the specified process(es).&lt;br /&gt;
&lt;br /&gt;
PDC interrupts are sent to all processes; other interrupts are only sent to the process with GPU rights.&lt;br /&gt;
&lt;br /&gt;
= Framebuffer Info =&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the top LCD is located at sharedMemBase + 0x200 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
The framebuffer info structure for the bottom LCD is located at sharedMemBase + 0x240 + (clientID * 0x80).&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
|  Framebuffer info [[GSPGPU:SetBufferSwap|entry]] index&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Flags (bit0 = client has set new data)&lt;br /&gt;
|-&lt;br /&gt;
| 3-2&lt;br /&gt;
| Padding&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Framebuffer Info Structure ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Active framebuffer (0 = first, 1 = second)&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Left framebuffer VA&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Right framebuffer VA (top screen only)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Stride]] (offset 0x90)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| [[GPU/External_Registers#Framebuffer_format|Format]]&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU/External_Registers#LCD_Source_Framebuffer_Setup|Status]] (offset 0x78)&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| ? (&amp;quot;Attribute&amp;quot;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When a process sets this framebuffer info, it sets index to &amp;lt;nowiki&amp;gt;(index+1) &amp;amp; 1&amp;lt;/nowiki&amp;gt;. Then it writes the framebuffer info entry, and sets flag to value 1. The GSP module loads this framebuffer info entry data into GSP state once the [[GPU]] finishes processing GX commands 3 or 4. Once the GSP module finishes loading this framebuffer info, it sets flag to value 0, then it will not load the framebuffer info again until flag is value 1. After loading this entry data into GSP state, the GSP module then writes this framebuffer state to the [[LCD]] registers. GSP module automatically updates the LCD framebuffer registers each time GX commands 3 or 4 finish, even when this shared memory data was not updated by the application.(GSP module toggles the active framebuffer register when automatically updating LCD registers, when shared memory data is not used)&lt;br /&gt;
&lt;br /&gt;
The two 0x1C-byte framebuffer info entries are located at framebufferinfo+4.&lt;br /&gt;
&lt;br /&gt;
= 3D Slider and 3D [[GSPGPU:SetLedForceOff|LED]] =&lt;br /&gt;
&lt;br /&gt;
See [[Configuration Memory]].&lt;br /&gt;
&lt;br /&gt;
= Command Queue =&lt;br /&gt;
&lt;br /&gt;
The command queue is located at sharedMemBase + 0x800 + (clientID * 0x200). It consists of an header followed by at most 15 command entries. Each command entry is of size 0x20 and has an header followed by command specific parameters.&lt;br /&gt;
&lt;br /&gt;
After adding a command, [[GSPGPU:TriggerCmdReqQueue|TriggerCmdReqQueue]] must be used to trigger GSP processing when the total commands field is value 1.&lt;br /&gt;
&lt;br /&gt;
== Command Queue Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Index of the command to process, this is incremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Total commands to process, this is incremented by the application when adding the command to the queue, and decremented by GSP before handling the command&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Flags (bit0 = completed?, bit7 = fatal error)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| ? (bit0 = set flags.bit0)&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Result code for the last GX command which failed&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Command Header ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Byte&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| Command ID&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| ?&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| ? (bit0 = set queue.flags.bit0 after processing)&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| When set, the command fails if GSP is busy handling any other command; otherwise, it only fails if GSP is busy handling a command of the same kind&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Commands ==&lt;br /&gt;
&lt;br /&gt;
Addresses specified in parameters are virtual addresses. For applications these are normally located in GSP memory, while for other processes they are located in VRAM.&lt;br /&gt;
&lt;br /&gt;
Address and size parameters except for command 0 and command 5 must be 8-byte aligned.&lt;br /&gt;
&lt;br /&gt;
=== Trigger DMA Request ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| u8 CommandID is 0x00&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Source address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Destination address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Size&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush source (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command is normally used to DMA data from the application GSP [[Memory_layout|heap]] to VRAM. When flushing is enabled and the source buffer is not located within VRAM, svcFlushProcessDataCache is used to flush the source buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Command List Processing ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| u8 CommandID is 0x01&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buffer size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Update gas additive blend results (0 = don&#039;t update, 1 = update)&lt;br /&gt;
|-&lt;br /&gt;
| 6-4&lt;br /&gt;
| Unused&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Flush buffer (0 = don&#039;t flush, 1 = flush)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified address to a physical address, then writes the physical address and size to the [[GPU]] registers at 0x1EF018E0. This buffer contains [[GPU/Internal_Registers|GPU commands]]. When flushing is enabled, svcFlushProcessDataCache is used to flush the buffer.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Memory Fill ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| u8 CommandID is 0x02&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 start address (0 = don&#039;t fill anything)&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 value&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf0 end address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 start address (0 = don&#039;t fill anything)&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf1 value&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf1 end address&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Control0 &amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt; (Control1 &amp;lt;&amp;lt; 16)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these addresses and the specified parameters to the [[GPU]] registers at 0x1EF00010 and 0x1EF00020. Doing so fills the specified buffers with the associated 4-byte value. This is used to clear GPU framebuffers.&lt;br /&gt;
The associated buffer address must not be &amp;lt;= to the main buffer address, thus the associated buffer address must not be zero as well. When the bufX address is zero, processing for the bufX parameters is skipped.&lt;br /&gt;
&lt;br /&gt;
The values of Control0 and Control1 give information about the type of memory fill. See [[GPU/External_Registers#Memory Fill|here]] for more information about memory fill parameters.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Display Transfer ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| u8 CommandID is 0x03&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output framebuffer address&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Input framebuffer [[GPU|dimensions]]&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Output framebuffer dimensions&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| [[GPU|Flags]], for applications this is 0x1001000 for the main screen, and 0x1000 for the sub screen.&lt;br /&gt;
|-&lt;br /&gt;
| 7-6&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command converts the specified addresses to physical addresses, then writes these physical addresses and parameters to the [[GPU]] registers at 0x1EF00C00. This GPU command copies the already rendered framebuffer data from the input GPU framebuffer address to the specified output LCD framebuffer. The input framebuffer is normally located in VRAM.&lt;br /&gt;
&lt;br /&gt;
The GPU color buffer is stored in the same Z-curve (tiled) format as textures. By default, SetDisplayTransfer converts the given buffer from the tiled format to a linear format adapted to the LCD framebuffers.&lt;br /&gt;
&lt;br /&gt;
Display transfers are performed asynchronously, so after requesting a display transfer you should wait for the PPF interrupt to fire before reading the output data.&lt;br /&gt;
&lt;br /&gt;
The minimum supported dimension for output is 64x64, anything lower will hang the engine.&lt;br /&gt;
&lt;br /&gt;
=== Trigger Texture Copy ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| u8 CommandID is 0x04&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Input buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Output buffer address.&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Total bytes to copy, not including gaps.&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Bits 0-15: Size of input line, in bytes. Bits 16-31: Gap between input lines, in bytes.&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Same as 4, but for the output.&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Flags, corresponding to the [[GPU/External_Registers#Transfer_Engine|Transfer Engine flags]]. However, for TextureCopy commands, bit 3 is always set, bit 2 is set if any output dimension is smaller than the input, and other bits are always 0.&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This command is similar to cmd3. It also triggers the [[GPU/External_Registers#Transfer_Engine|GPU Transfer Engine]], but setting the TextureCopy parameters.&lt;br /&gt;
&lt;br /&gt;
=== Flush Cache Regions ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!  Index Word&lt;br /&gt;
!  Description&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| u8 CommandID is 0x05&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| Buf0 address&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| Buf0 size&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| Buf1 address&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| Buf1 size&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| Buf2 address&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| Buf2 size&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| Unused&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The application buffer addresses specified in the parameters are used with [[SVC|svcFlushProcessDataCache]]. The input buf0 size must not be zero. When buf1 size is zero, svcFlushProcessDataCache() for buf1 and buf2 are skipped. When buf2 size is zero, svcFlushProcessDataCache() for buf2 is skipped.&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=GSPGPU:WriteHWRegsWithMask&amp;diff=22912</id>
		<title>GSPGPU:WriteHWRegsWithMask</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=GSPGPU:WriteHWRegsWithMask&amp;diff=22912"/>
		<updated>2024-12-15T13:48:02Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Fix formatting&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=Request=&lt;br /&gt;
{{IPC/Request|Request}}&lt;br /&gt;
{{IPC/RequestHeader|0x0002|2|4}}&lt;br /&gt;
{{IPC/RequestEntry|[[GPU]] address based at 0x1EB00000, must be word-aligned}}&lt;br /&gt;
{{IPC/RequestEntry|&amp;lt;nowiki&amp;gt;Size, must be &amp;lt;=0x80 and word-aligned&amp;lt;/nowiki&amp;gt;}}&lt;br /&gt;
{{IPC/TranslateStaticBuffer|Data pointer|0}}&lt;br /&gt;
{{IPC/TranslateStaticBuffer|Mask data pointer|1}}&lt;br /&gt;
{{IPC/RequestEnd}}&lt;br /&gt;
&lt;br /&gt;
=Response=&lt;br /&gt;
{{IPC/Request|Response}}&lt;br /&gt;
{{IPC/RequestHeader|0x0002|2|0}}&lt;br /&gt;
{{IPC/RequestEntry|Result code}}&lt;br /&gt;
{{IPC/RequestEnd}}&lt;br /&gt;
&lt;br /&gt;
=Description=&lt;br /&gt;
The GPU register offset must be &amp;lt;0x420000. GPU register = (register &amp;amp; ~maskword) | (data &amp;amp; maskword).&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
	<entry>
		<id>https://www.3dbrew.org/w/index.php?title=PS:VerifyRsaSha256&amp;diff=22893</id>
		<title>PS:VerifyRsaSha256</title>
		<link rel="alternate" type="text/html" href="https://www.3dbrew.org/w/index.php?title=PS:VerifyRsaSha256&amp;diff=22893"/>
		<updated>2024-12-14T20:22:30Z</updated>

		<summary type="html">&lt;p&gt;Kynex7510: Fix formatting&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=Request=&lt;br /&gt;
{{IPC/Request}}&lt;br /&gt;
{{#vardefine:ipc_offset|0}}&lt;br /&gt;
{{IPC/RequestEntry|Header code [0x00020244]}}&lt;br /&gt;
{{IPC/RequestEntryRange|8|SHA256 hash to compare with.}}&lt;br /&gt;
{{IPC/RequestEntry|Unused. Intended as the signature size.}}&lt;br /&gt;
{{IPC/TranslateStaticBuffer|RSA [[Process_Services_PXI|context]] buffer|0}}&lt;br /&gt;
{{IPC/MapPointerR|Signature buffer}}&lt;br /&gt;
{{IPC/RequestEnd}}&lt;br /&gt;
&lt;br /&gt;
=Response=&lt;br /&gt;
{{IPC/Request}}&lt;br /&gt;
{{#vardefine:ipc_offset|0}}&lt;br /&gt;
{{IPC/RequestEntry|Header code}}&lt;br /&gt;
{{IPC/RequestEntry|Result code}}&lt;br /&gt;
{{IPC/MapPointerR|Signature buffer. Size for the translate-header is hard-coded to 0x100.}}&lt;br /&gt;
{{IPC/RequestEnd}}&lt;/div&gt;</summary>
		<author><name>Kynex7510</name></author>
	</entry>
</feed>