<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://hedgedocs.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Superevil</id>
	<title>HedgeDocs - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://hedgedocs.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Superevil"/>
	<link rel="alternate" type="text/html" href="https://hedgedocs.com/index.php/Special:Contributions/Superevil"/>
	<updated>2026-10-07T08:19:30Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://hedgedocs.com/index.php?title=Sonic_Origins_Character_Select&amp;diff=1573</id>
		<title>Sonic Origins Character Select</title>
		<link rel="alternate" type="text/html" href="https://hedgedocs.com/index.php?title=Sonic_Origins_Character_Select&amp;diff=1573"/>
		<updated>2026-10-05T13:37:56Z</updated>

		<summary type="html">&lt;p&gt;Superevil: Added page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Notice|type=info|content=This research was done with heavy AI assistance (Claude), directed and tested in game by Superevil for the NoSwap mod. It is shared freely for anyone to use.&amp;lt;br/&amp;gt;&lt;br /&gt;
All addresses are for the &#039;&#039;&#039;Steam&#039;&#039;&#039; release, &#039;&#039;&#039;Steam build ID 12197262&#039;&#039;&#039; (the executable&#039;s version resource reads &#039;&#039;&#039;2.0.2.2667&#039;&#039;&#039;). The build ID is the one the research was done against. The file version was read from the same installed executable; the DLL itself never checks it.&amp;lt;br/&amp;gt;&lt;br /&gt;
Addresses are build-specific. Never trust an address blindly: before patching, compare the first bytes of every function it hooks with the bytes found in this build (listed in [[#Re-finding the functions|Re-finding the functions]]) and leave the game alone on any mismatch. After a game update, search the executable for those byte patterns to find the new addresses.}}&#039;&#039;&#039;Sonic Origins&#039;&amp;lt;nowiki/&amp;gt;&#039;&#039;&#039; character select for the classic games is a popup window drawn by the Hedgehog Engine side of the game, not by the Retro Engine (RSDK) games themselves. This page documents how it works internally (the data tables, the functions that fill it, the save records behind it and the menu archives that draw it) and [[Sonic Origins Character Select#Adding cards|how to add new character cards]] to it.&lt;br /&gt;
&lt;br /&gt;
Everything here applies to &#039;&#039;&#039;Sonic 1, Sonic 2 and Sonic CD&#039;&#039;&#039;. Sonic 3 &amp;amp; Knuckles picks its character on its own save screen and is left out.&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
After a mode is picked in the main menu, each classic game&#039;s title screen opens the popup &amp;lt;code&amp;gt;UICharacterSelectWindow&amp;lt;/code&amp;gt;. It shows up to &#039;&#039;&#039;7 cards&#039;&#039;&#039; (casts &amp;lt;code&amp;gt;obj_btn_1&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;obj_btn_7&amp;lt;/code&amp;gt;), one per playable &#039;&#039;&#039;kind&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
A &#039;&#039;&#039;kind&#039;&#039;&#039; is a byte that is the classic game&#039;s own character ID. The picked kind reaches the Sonic 1, Sonic 2 and Sonic CD scripts unchanged as &amp;lt;code&amp;gt;stage.playerListPos&amp;lt;/code&amp;gt;:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;margin: auto;&amp;quot; width=&amp;quot;60%&amp;quot;&lt;br /&gt;
!Kind&lt;br /&gt;
!Character&lt;br /&gt;
|-&lt;br /&gt;
|0&lt;br /&gt;
|Sonic&lt;br /&gt;
|-&lt;br /&gt;
|1&lt;br /&gt;
|Tails&lt;br /&gt;
|-&lt;br /&gt;
|2&lt;br /&gt;
|Knuckles&lt;br /&gt;
|-&lt;br /&gt;
|3&lt;br /&gt;
|Sonic + Tails&lt;br /&gt;
|-&lt;br /&gt;
|4&lt;br /&gt;
|Knuckles + Tails&lt;br /&gt;
|-&lt;br /&gt;
|5&lt;br /&gt;
|Amy&lt;br /&gt;
|-&lt;br /&gt;
|6&lt;br /&gt;
|Amy + Tails&lt;br /&gt;
|}&lt;br /&gt;
In the games&#039; own data these match the GameConfig constants (Sonic 1: &amp;lt;code&amp;gt;PLAYER_SONIC&amp;lt;/code&amp;gt; = 0, &amp;lt;code&amp;gt;TAILS&amp;lt;/code&amp;gt; = 1, &amp;lt;code&amp;gt;KNUCKLES&amp;lt;/code&amp;gt; = 2, &amp;lt;code&amp;gt;SONIC_TAILS&amp;lt;/code&amp;gt; = 3, &amp;lt;code&amp;gt;AMY&amp;lt;/code&amp;gt; = 5, &amp;lt;code&amp;gt;AMY_TAILS&amp;lt;/code&amp;gt; = 6; ID 4 is unused in Sonic 1). The Retro Engine never bounds-checks &amp;lt;code&amp;gt;stage.playerListPos&amp;lt;/code&amp;gt;, so every per-character behaviour is decided by the scripts.&lt;br /&gt;
&lt;br /&gt;
Games are numbered in RSDK order everywhere below: &#039;&#039;&#039;0&#039;&#039;&#039; Sonic 1, &#039;&#039;&#039;1&#039;&#039;&#039; Sonic 2, &#039;&#039;&#039;2&#039;&#039;&#039; Sonic 3 &amp;amp; Knuckles, &#039;&#039;&#039;3&#039;&#039;&#039; Sonic CD. Modes are numbered 0 to 3.&lt;br /&gt;
&lt;br /&gt;
The image base is fixed at &amp;lt;code&amp;gt;0x140000000&amp;lt;/code&amp;gt;, and the executable has no relocations, so the addresses below are the same every run.&lt;br /&gt;
&lt;br /&gt;
== Data tables ==&lt;br /&gt;
=== The kind table ===&lt;br /&gt;
At &amp;lt;code&amp;gt;0x140D586A0&amp;lt;/code&amp;gt;: 7 entries of 24 bytes (one per kind 0 to 6), each three pointers to C strings:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;margin: auto;&amp;quot; width=&amp;quot;90%&amp;quot;&lt;br /&gt;
!Offset&lt;br /&gt;
!Type&lt;br /&gt;
!Meaning&lt;br /&gt;
|-&lt;br /&gt;
|0x00&lt;br /&gt;
|&amp;lt;code&amp;gt;const char*&amp;lt;/code&amp;gt;&lt;br /&gt;
|SurfRide animation (pattern) that shows the card&#039;s picture: &amp;lt;code&amp;gt;PRM_chara_&amp;amp;#x3C;kind+1&amp;amp;#x3E;&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|0x08&lt;br /&gt;
|&amp;lt;code&amp;gt;const char*&amp;lt;/code&amp;gt;&lt;br /&gt;
|Text key of the card&#039;s first name line (&amp;lt;code&amp;gt;MAINMENU_character_name_*&amp;lt;/code&amp;gt;)&lt;br /&gt;
|-&lt;br /&gt;
|0x10&lt;br /&gt;
|&amp;lt;code&amp;gt;const char*&amp;lt;/code&amp;gt;&lt;br /&gt;
|Text key of the card&#039;s second name line&lt;br /&gt;
|}&lt;br /&gt;
The second line is only shown for the &amp;quot;+ Tails&amp;quot; kinds. That test is &amp;lt;code&amp;gt;KindHasTails(kind)&amp;lt;/code&amp;gt; at &amp;lt;code&amp;gt;0x140331060&amp;lt;/code&amp;gt;. The vanilla name keys in &amp;lt;code&amp;gt;text_menu_&amp;amp;#x3C;language&amp;amp;#x3E;.pac&amp;lt;/code&amp;gt; are &amp;lt;code&amp;gt;MAINMENU_character_name_sonic&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;_tails&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;_knuckles&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;_amy&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The table ends exactly where Sonic 1&#039;s row table begins (&amp;lt;code&amp;gt;0x140D586A0 + 7 × 24 = 0x140D58748&amp;lt;/code&amp;gt;), so it cannot be grown in place.&lt;br /&gt;
&lt;br /&gt;
=== The row tables ===&lt;br /&gt;
Each game has a table of &#039;&#039;&#039;7 rows of 7 bytes&#039;&#039;&#039;. Each row lists the kinds to show, in card order. &amp;lt;code&amp;gt;0xFF&amp;lt;/code&amp;gt; marks an unused card. The unlock state and Origins Plus pick which row is used.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;margin: auto;&amp;quot; width=&amp;quot;90%&amp;quot;&lt;br /&gt;
!Address&lt;br /&gt;
!Game&lt;br /&gt;
!Notes&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140D58748&amp;lt;/code&amp;gt;&lt;br /&gt;
|Sonic 1&lt;br /&gt;
|Rows 4 to 6 (Plus): &amp;lt;code&amp;gt;0 1 2 5&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140D58780&amp;lt;/code&amp;gt;&lt;br /&gt;
|Sonic 2&lt;br /&gt;
|Plus row: &amp;lt;code&amp;gt;0 1 2 5 3 4 6&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140D587B8&amp;lt;/code&amp;gt;&lt;br /&gt;
|Sonic CD&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140D587F0&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x140D58828&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x140D58860&amp;lt;/code&amp;gt;&lt;br /&gt;
|Not identified&lt;br /&gt;
|Three more tables with the same 0x38 spacing&lt;br /&gt;
|}&lt;br /&gt;
Sonic 1&#039;s 49 bytes in this build, row by row:&amp;lt;pre&amp;gt;&lt;br /&gt;
row 0: 00 01 02 FF FF FF FF&lt;br /&gt;
row 1: 00 01 02 FF FF FF FF&lt;br /&gt;
row 2: 00 FF FF FF FF FF FF&lt;br /&gt;
row 3: 00 01 02 FF FF FF FF&lt;br /&gt;
row 4: 00 01 02 05 FF FF FF&lt;br /&gt;
row 5: 00 01 02 05 FF FF FF&lt;br /&gt;
row 6: 00 01 02 05 FF FF FF&lt;br /&gt;
&amp;lt;/pre&amp;gt;Which unlock state selects rows 0 to 3 was not worked out.&lt;br /&gt;
&lt;br /&gt;
The row picker is the function at &amp;lt;code&amp;gt;0x1403E0D80&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;const uint8* RowPicker(uint8 game, uint8 mode, uint8 a, uint8 b)&amp;lt;/code&amp;gt;. It returns a pointer to the chosen 7-byte row, which it indexes with an &amp;lt;code&amp;gt;imul 7&amp;lt;/code&amp;gt;. The meaning of &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;b&amp;lt;/code&amp;gt; was not identified. Rows are 7 wide in code, which matches the 7 card casts.&lt;br /&gt;
&lt;br /&gt;
=== Special values ===&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;0xFF&amp;lt;/code&amp;gt; in a row is an empty card. That is why the highest usable kind is 254.&lt;br /&gt;
* &#039;&#039;&#039;Kind 3 fallback:&#039;&#039;&#039; &amp;lt;code&amp;gt;KindAllowedInGame(kind, game)&amp;lt;/code&amp;gt; at &amp;lt;code&amp;gt;0x140331080&amp;lt;/code&amp;gt; starts with &amp;lt;code&amp;gt;cmp cl, 6 / ja&amp;lt;/code&amp;gt;, so it rejects every kind above 6. A rejected kind is replaced with &#039;&#039;&#039;3&#039;&#039;&#039; (Sonic &amp;amp; Tails) before the game starts. Any new kind needs this check to let it through.&lt;br /&gt;
* &#039;&#039;&#039;&amp;quot;kind ≤ 6&amp;quot; checks:&#039;&#039;&#039; the popup&#039;s build function calls a shared helper at &amp;lt;code&amp;gt;0x140331220&amp;lt;/code&amp;gt; to test that a kind is in range. Other 7-value enums (the MapArea enum, for one) use the same helper, so only the popup&#039;s own call sites may be redirected, never the helper itself.&lt;br /&gt;
&lt;br /&gt;
== The functions ==&lt;br /&gt;
=== Building the popup ===&lt;br /&gt;
The popup&#039;s build function is &amp;lt;code&amp;gt;0x1403E0030&amp;lt;/code&amp;gt; (vtable slot 6 of &amp;lt;code&amp;gt;UICharacterSelectWindow&amp;lt;/code&amp;gt;). It fills an array of &#039;&#039;&#039;16-byte card entries&#039;&#039;&#039; at window &amp;lt;code&amp;gt;+0x2B0&amp;lt;/code&amp;gt; (count at &amp;lt;code&amp;gt;+0x2B8&amp;lt;/code&amp;gt;), one per card:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;margin: auto;&amp;quot; width=&amp;quot;90%&amp;quot;&lt;br /&gt;
!Offset&lt;br /&gt;
!Meaning&lt;br /&gt;
|-&lt;br /&gt;
|0x00&lt;br /&gt;
|Kind&lt;br /&gt;
|-&lt;br /&gt;
|0x01&lt;br /&gt;
|Zone of the save&lt;br /&gt;
|-&lt;br /&gt;
|0x02 to 0x0C&lt;br /&gt;
|Mission data (not decoded)&lt;br /&gt;
|-&lt;br /&gt;
|0x0D&lt;br /&gt;
|Flags: bit 1 = has a save, bit 0 = cleared&lt;br /&gt;
|}&lt;br /&gt;
The card loop is at about &amp;lt;code&amp;gt;0x1403E09D0&amp;lt;/code&amp;gt;, inside the build function. For each entry it finds the cast &amp;lt;code&amp;gt;obj_btn_%d&amp;lt;/code&amp;gt; in the layout and plays the kind&#039;s pattern from the kind table. It then plays &amp;lt;code&amp;gt;PRM_save_icon_on&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;PRM_save_icon_off&amp;lt;/code&amp;gt; and sets the name text casts &amp;lt;code&amp;gt;sysf_btn_name_1&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;sysf_btn_name_2&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
These are the build function&#039;s references that matter when adding kinds (offsets from &amp;lt;code&amp;gt;0x1403E0030&amp;lt;/code&amp;gt;):&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;margin: auto;&amp;quot; width=&amp;quot;90%&amp;quot;&lt;br /&gt;
!Offset&lt;br /&gt;
!Instruction&lt;br /&gt;
!Target&lt;br /&gt;
|-&lt;br /&gt;
| +0x78, +0x183, +0x1EE, +0x9AC, +0xB3E&lt;br /&gt;
|&amp;lt;code&amp;gt;call&amp;lt;/code&amp;gt;&lt;br /&gt;
|&amp;quot;kind ≤ 6&amp;quot; helper &amp;lt;code&amp;gt;0x140331220&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| +0xA01, +0xA9B, +0xB0B&lt;br /&gt;
|&amp;lt;code&amp;gt;lea r64, [rip+disp32]&amp;lt;/code&amp;gt;&lt;br /&gt;
|Kind table &amp;lt;code&amp;gt;0x140D586A0&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| +0xAB1&lt;br /&gt;
|&amp;lt;code&amp;gt;call&amp;lt;/code&amp;gt;&lt;br /&gt;
|&amp;lt;code&amp;gt;KindHasTails&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;0x140331060&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
A seventh &amp;quot;kind ≤ 6&amp;quot; call is in another of the popup&#039;s functions, at &amp;lt;code&amp;gt;0x1403E0F10 + 0x47&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Seen in testing (by temporarily editing Sonic 1&#039;s row bytes): the window shows 5, 6 or 7 cards and re-centres and resizes itself automatically. A duplicated kind works as a card (it highlights, shows its save and launches). The window&#039;s layer has animations &amp;lt;code&amp;gt;PRM_btn_2&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;PRM_btn_7&amp;lt;/code&amp;gt;, which are probably what lays out each card count (unconfirmed).&lt;br /&gt;
&lt;br /&gt;
=== Window and cursor structures ===&lt;br /&gt;
Known fields of &amp;lt;code&amp;gt;UICharacterSelectWindow&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;+0x251&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;+0x250&amp;lt;/code&amp;gt;: the two bytes the popup passes to the save views as their &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;b&amp;lt;/code&amp;gt; arguments (probably mode and game);&lt;br /&gt;
* &amp;lt;code&amp;gt;+0x264&amp;lt;/code&amp;gt;: handle of its cursor controller;&lt;br /&gt;
* &amp;lt;code&amp;gt;+0x2A8&amp;lt;/code&amp;gt;: layer &amp;lt;code&amp;gt;lay&amp;lt;/code&amp;gt;;&lt;br /&gt;
* &amp;lt;code&amp;gt;+0x2B0&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;+0x2B8&amp;lt;/code&amp;gt;: the card entries and their count.&lt;br /&gt;
&lt;br /&gt;
Its cursor controller is a &amp;lt;code&amp;gt;ui::MenuItemContainer&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;+0x250&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;+0x258&amp;lt;/code&amp;gt;: the &amp;quot;cursor moved&amp;quot; delegates and their count. Element 0 is the popup&#039;s: vtable &amp;lt;code&amp;gt;0x140D58B78&amp;lt;/code&amp;gt;, popup pointer at &amp;lt;code&amp;gt;+0x8&amp;lt;/code&amp;gt;, handler &amp;lt;code&amp;gt;0x1403E10A0&amp;lt;/code&amp;gt; at &amp;lt;code&amp;gt;+0x10&amp;lt;/code&amp;gt;. This is a reliable way to tell the character select&#039;s container apart from every other menu list.&lt;br /&gt;
* &amp;lt;code&amp;gt;+0x290&amp;lt;/code&amp;gt;: the &amp;quot;decide&amp;quot; delegates, fired by &amp;lt;code&amp;gt;FireDelegates(ctrl, ctrl+0x290, index)&amp;lt;/code&amp;gt; at &amp;lt;code&amp;gt;0x1403BF8D0&amp;lt;/code&amp;gt;;&lt;br /&gt;
* &amp;lt;code&amp;gt;+0x2EC&amp;lt;/code&amp;gt;: current index;&lt;br /&gt;
* &amp;lt;code&amp;gt;+0x2F0&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;+0x2F8&amp;lt;/code&amp;gt;: items (&amp;lt;code&amp;gt;MenuItem*&amp;lt;/code&amp;gt;, with the card&#039;s cast at item &amp;lt;code&amp;gt;+0x18&amp;lt;/code&amp;gt;);&lt;br /&gt;
* &amp;lt;code&amp;gt;+0x318&amp;lt;/code&amp;gt;: flags, where bit 2 means input is enabled.&lt;br /&gt;
&lt;br /&gt;
The popup keeps nothing per card beyond the entries: OK and the save line read entry &#039;&#039;n&#039;&#039; by the cursor index.&lt;br /&gt;
&lt;br /&gt;
=== Paging (NextEnabledIndex / PrevEnabledIndex) ===&lt;br /&gt;
The cursor moves through &amp;lt;code&amp;gt;NextEnabledIndex&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;0x1403BFBB0&amp;lt;/code&amp;gt;) and &amp;lt;code&amp;gt;PrevEnabledIndex&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;0x1403BFC40&amp;lt;/code&amp;gt;), both &amp;lt;code&amp;gt;int (MenuItemContainer* ctrl, int index)&amp;lt;/code&amp;gt;. &amp;lt;code&amp;gt;MenuItemContainer::Update&amp;lt;/code&amp;gt; is at &amp;lt;code&amp;gt;0x1403C0810&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The popup has only 7 card slots, so a longer list needs a sliding window of 7. Moving right from the last card, or left from the first, scrolls the list by one (wrapping at the ends) and refills the 7 cards in place with the game&#039;s own calls:&lt;br /&gt;
&lt;br /&gt;
# Copy each card&#039;s 16-byte entry into window &amp;lt;code&amp;gt;+0x2B0&amp;lt;/code&amp;gt;.&lt;br /&gt;
# &amp;lt;code&amp;gt;PlayAnim(card, pattern, 0)&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;0x140481C20&amp;lt;/code&amp;gt;) with the kind&#039;s picture pattern, then with &amp;lt;code&amp;gt;PRM_save_icon_on&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;_off&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Get the layout component: &amp;lt;code&amp;gt;GetComponent(window, 0x142882E30)&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;0x1405C6FD0&amp;lt;/code&amp;gt;).&lt;br /&gt;
# Find each name text: &amp;lt;code&amp;gt;FindChild(component, &amp;quot;obj_btn_N&amp;quot;, &amp;quot;sysf_btn_name_1&amp;quot;)&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;0x14047F760&amp;lt;/code&amp;gt;), and the same for &amp;lt;code&amp;gt;_2&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Set each name: &amp;lt;code&amp;gt;SetTextKey(textCast, key)&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;0x140483330&amp;lt;/code&amp;gt;), with &amp;lt;code&amp;gt;&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; to hide a line.&lt;br /&gt;
# Redraw the save line under the cards: &amp;lt;code&amp;gt;UpdateSaveInfo(window, index)&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;0x1403E1140&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
The cursor index is returned unchanged, so the cursor stays where it is while the cards change. A hook on the build function (&amp;lt;code&amp;gt;0x1403E0030&amp;lt;/code&amp;gt;) resets the page and records each kind&#039;s entry when the popup opens.&lt;br /&gt;
&lt;br /&gt;
=== Other helpers ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;margin: auto;&amp;quot; width=&amp;quot;90%&amp;quot;&lt;br /&gt;
!Address&lt;br /&gt;
!Name (unofficial)&lt;br /&gt;
!Notes&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x1405C6420&amp;lt;/code&amp;gt;&lt;br /&gt;
|ResolveHandle&lt;br /&gt;
|Handle to object&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x14047F7A0&amp;lt;/code&amp;gt;&lt;br /&gt;
|FindCast(component, name)&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x142882E78&amp;lt;/code&amp;gt;&lt;br /&gt;
|(text cast class)&lt;br /&gt;
|Compared against a cast&#039;s class chain before &amp;lt;code&amp;gt;SetTextKey&amp;lt;/code&amp;gt;, as &amp;lt;code&amp;gt;UpdateSaveInfo&amp;lt;/code&amp;gt; does at &amp;lt;code&amp;gt;0x1403E11C5&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140454170&amp;lt;/code&amp;gt;&lt;br /&gt;
|PlaySfx(out, window, sound)&lt;br /&gt;
|Playing sound 0 after a page refill works as the cursor sound (assumed)&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140483A00&amp;lt;/code&amp;gt;&lt;br /&gt;
|SetCropIndex(layer, cast, index)&lt;br /&gt;
|Sets an image cast&#039;s crop&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Launch: how a kind reaches the game ===&lt;br /&gt;
When a card is picked, its kind is stored as the &amp;quot;last kind&amp;quot; for (mode, game) and the game starts with it. Testing shows the kind arrives in Sonic 1, Sonic 2 and Sonic CD as &amp;lt;code&amp;gt;stage.playerListPos&amp;lt;/code&amp;gt; unchanged (kinds 7 and up launch correctly once &amp;lt;code&amp;gt;KindAllowedInGame&amp;lt;/code&amp;gt; lets them through). The exact native code that writes &amp;lt;code&amp;gt;playerListPos&amp;lt;/code&amp;gt; was not traced. The research notes place the kind 3 replacement in &amp;lt;code&amp;gt;RetroEngineBase::Setup&amp;lt;/code&amp;gt; (unconfirmed by name).&lt;br /&gt;
&lt;br /&gt;
The launch paths that read the last kind (all calls to &amp;lt;code&amp;gt;GetLastKind&amp;lt;/code&amp;gt;, below):&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;0x1403546D9&amp;lt;/code&amp;gt;: the menu scene&#039;s launch state (function &amp;lt;code&amp;gt;0x140354080&amp;lt;/code&amp;gt;). A kind that the row check &amp;lt;code&amp;gt;RowHasKind(game, mode, kind, a, b)&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;0x1403E0FA0&amp;lt;/code&amp;gt;, called at &amp;lt;code&amp;gt;0x140354705&amp;lt;/code&amp;gt;) rejects becomes Sonic, or Sonic &amp;amp; Tails in Sonic 2.&lt;br /&gt;
* &amp;lt;code&amp;gt;0x14033A422&amp;lt;/code&amp;gt;: the game scene&#039;s setup from a queued start request (function &amp;lt;code&amp;gt;0x14033A070&amp;lt;/code&amp;gt;). It stores the kind at &amp;lt;code&amp;gt;+0xC9&amp;lt;/code&amp;gt; and then opens the 16-byte save view with it. A pick in the popup can reach the game this way too, so a mod that changes the kind must handle both paths.&lt;br /&gt;
&lt;br /&gt;
=== Saves: SlotIndex and the save views ===&lt;br /&gt;
Origins keeps its classic-game saves as records per (mode, game, kind) slot. Every record lookup goes through &amp;lt;code&amp;gt;SlotIndex(mode, game, kind)&amp;lt;/code&amp;gt; at &amp;lt;code&amp;gt;0x14045DA50&amp;lt;/code&amp;gt;. Its tables are indexed &amp;lt;code&amp;gt;[game * 7 + kind]&amp;lt;/code&amp;gt; (74 slots). It has &#039;&#039;&#039;no slot for kinds 7 and up&#039;&#039;&#039;: callers then get a null record and crash.&lt;br /&gt;
&lt;br /&gt;
Records are reached through four &#039;&#039;&#039;views&#039;&#039;&#039;, each &amp;lt;code&amp;gt;View(this, out, a, b, kind)&amp;lt;/code&amp;gt;, which fill &amp;lt;code&amp;gt;out = {vtable, record*, owner}&amp;lt;/code&amp;gt;:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;margin: auto;&amp;quot; width=&amp;quot;90%&amp;quot;&lt;br /&gt;
!Address&lt;br /&gt;
!Record size&lt;br /&gt;
!Contents&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140459B60&amp;lt;/code&amp;gt;&lt;br /&gt;
|0x10&lt;br /&gt;
|Byte 0 is 1 when there is a save, byte 1 is the zone, and bit 25 of the u32 at +4 means cleared&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140459420&amp;lt;/code&amp;gt;&lt;br /&gt;
|0xA0&lt;br /&gt;
|Sonic 1 keeps its classic save here (the first 0xA0 bytes of the engine save data)&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140459360&amp;lt;/code&amp;gt;&lt;br /&gt;
|0x8000&lt;br /&gt;
|The classic game&#039;s save RAM (not used by Sonic 1)&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x14045A190&amp;lt;/code&amp;gt;&lt;br /&gt;
|0x10001&lt;br /&gt;
|Not decoded&lt;br /&gt;
|}&lt;br /&gt;
Origins keeps a separate, complete save RAM array per character. Entering a classic game loads the last-played character&#039;s array.&lt;br /&gt;
&lt;br /&gt;
=== The CONTINUE head (main menu) ===&lt;br /&gt;
The main menu&#039;s CONTINUE button shows the head of the character last played in that (mode, game). Origins stores one &amp;quot;last kind&amp;quot; per (mode 0 to 3, game) in its save:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;GetLastKind(save, mode, game)&amp;lt;/code&amp;gt; at &amp;lt;code&amp;gt;0x14045A110&amp;lt;/code&amp;gt; (it reads save bytes &amp;lt;code&amp;gt;+0x06&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;+0x0A&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;+0x12&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;+0x0E&amp;lt;/code&amp;gt;);&lt;br /&gt;
* &amp;lt;code&amp;gt;SetLastKind(save, mode, game, kind)&amp;lt;/code&amp;gt; at &amp;lt;code&amp;gt;0x14045CB50&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The main menu reads it at &amp;lt;code&amp;gt;0x1403E509D&amp;lt;/code&amp;gt;, in its fill function &amp;lt;code&amp;gt;0x1403E4F10&amp;lt;/code&amp;gt;, which builds the table of the buttons&#039; kinds. The CONTINUE button reads it at &amp;lt;code&amp;gt;0x1403E92AD&amp;lt;/code&amp;gt;, in &amp;lt;code&amp;gt;0x1403E8FC0&amp;lt;/code&amp;gt;, to find the kind whose save must exist (&amp;lt;code&amp;gt;0x1403E70D0&amp;lt;/code&amp;gt;). The launch reads above use it too.&lt;br /&gt;
&lt;br /&gt;
The head is drawn by &amp;lt;code&amp;gt;SetIconChara(menu, layer, kind)&amp;lt;/code&amp;gt; at &amp;lt;code&amp;gt;0x1403E7830&amp;lt;/code&amp;gt;, called at &amp;lt;code&amp;gt;0x1403E8436&amp;lt;/code&amp;gt; (the main menu&#039;s fill) and &amp;lt;code&amp;gt;0x1403E94D3&amp;lt;/code&amp;gt; (after a pick in the select). It rejects kinds above 6. It reads a crop table at &amp;lt;code&amp;gt;0x140D59518&amp;lt;/code&amp;gt;, whose kind 0 entry is &amp;lt;code&amp;gt;00 00 00 00 FF FF FF FF&amp;lt;/code&amp;gt;: crop 0 on cast &amp;lt;code&amp;gt;pattern_chara_1&amp;lt;/code&amp;gt; and none (−1) on &amp;lt;code&amp;gt;pattern_chara_2&amp;lt;/code&amp;gt;. It then calls &amp;lt;code&amp;gt;SetCropIndex&amp;lt;/code&amp;gt;. The layout of the remaining entries is assumed from that first one.&lt;br /&gt;
&lt;br /&gt;
The head texture is &amp;lt;code&amp;gt;menu_main_menu_icon_chara_s.dds&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;ui_mainmenu.pac&amp;lt;/code&amp;gt; (block &amp;lt;code&amp;gt;.004&amp;lt;/code&amp;gt;). It is BC7, 480×96, holding 4 cells of 120×96 (Sonic, Tails, Knuckles, Amy), and is drawn at 4 texels per pixel. The scene is &amp;lt;code&amp;gt;menu_main_menu_island.swif&amp;lt;/code&amp;gt;, which is in each of the 12 language archives &amp;lt;code&amp;gt;ui_mainmenu_&amp;amp;#x3C;language&amp;amp;#x3E;.pac&amp;lt;/code&amp;gt; (de, en, es, fr, it, ja, ko, pl, pt, ru, zh, zhs). Its layer &amp;lt;code&amp;gt;ref_icon_chara&amp;lt;/code&amp;gt; has the casts &amp;lt;code&amp;gt;pattern_chara_1&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;_2&amp;lt;/code&amp;gt; and the animations &amp;lt;code&amp;gt;PRM_chara_1&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;_2&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The CONTINUE button is greyed out in Classic mode even in the vanilla game (that mode has no resume).&lt;br /&gt;
&lt;br /&gt;
== The menu archives ==&lt;br /&gt;
=== Which archive the popup uses ===&lt;br /&gt;
The scene &amp;lt;code&amp;gt;menu_main_menu_chara_select&amp;lt;/code&amp;gt; exists in two archives under &amp;lt;code&amp;gt;image/x64/raw/ui/&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;ui_gamestage.pac&amp;lt;/code&amp;gt;: &#039;&#039;&#039;the popup over the classic games&#039; title screens loads this one&#039;&#039;&#039;. Editing only &amp;lt;code&amp;gt;ui_mainmenu.pac&amp;lt;/code&amp;gt; changes nothing in the popup.&lt;br /&gt;
* &amp;lt;code&amp;gt;ui_mainmenu.pac&amp;lt;/code&amp;gt;: the main menu&#039;s own copy of the same scene.&lt;br /&gt;
&lt;br /&gt;
In both, the scene file (&amp;lt;code&amp;gt;.swif&amp;lt;/code&amp;gt;) is in the root, while the textures (&amp;lt;code&amp;gt;menu_main_menu_chara_select_chara_sonic.dds&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;_tails&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;_knuckles&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;_amy&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;_tails_parts&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;_btn_bg&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;_icon_save&amp;lt;/code&amp;gt;) are in a dependency block: &amp;lt;code&amp;gt;.000&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;ui_gamestage.pac&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.004&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;ui_mainmenu.pac&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== PACx v403 (split) layout ===&lt;br /&gt;
Origins&#039; &amp;lt;code&amp;gt;.pac&amp;lt;/code&amp;gt; files are a &#039;&#039;&#039;PACx v4.03&#039;&#039;&#039; outer archive around &#039;&#039;&#039;PACx v4.02&#039;&#039;&#039; inner archives.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Outer (PACx403)&#039;&#039;&#039;: a 0x30-byte header (signature &amp;lt;code&amp;gt;PACx&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;403&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;L&amp;lt;/code&amp;gt;), with the fields used here:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;margin: auto;&amp;quot; width=&amp;quot;90%&amp;quot;&lt;br /&gt;
!Offset&lt;br /&gt;
!Meaning&lt;br /&gt;
|-&lt;br /&gt;
|0x0C&lt;br /&gt;
|File size&lt;br /&gt;
|-&lt;br /&gt;
|0x10&lt;br /&gt;
|Root offset&lt;br /&gt;
|-&lt;br /&gt;
|0x14&lt;br /&gt;
|Root compressed size&lt;br /&gt;
|-&lt;br /&gt;
|0x18&lt;br /&gt;
|Root uncompressed size&lt;br /&gt;
|-&lt;br /&gt;
|0x24&lt;br /&gt;
|Size of the root chunk table, padded to 8&lt;br /&gt;
|-&lt;br /&gt;
|0x30&lt;br /&gt;
|Root chunk table: &amp;lt;code&amp;gt;u32 count&amp;lt;/code&amp;gt;, then &amp;lt;code&amp;gt;{u32 compressed, u32 uncompressed}&amp;lt;/code&amp;gt; per chunk&lt;br /&gt;
|}&lt;br /&gt;
After the table come the dependency &#039;&#039;&#039;blocks&#039;&#039;&#039; (&amp;lt;code&amp;gt;.pac.000&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.pac.001&amp;lt;/code&amp;gt;, ...), each starting on a 16-byte boundary. The root follows them, and the file is padded to 16. Every block and the root are split into &#039;&#039;&#039;64 KB pieces&#039;&#039;&#039;, each compressed on its own as an &#039;&#039;&#039;LZ4 block&#039;&#039;&#039; (a chunk whose compressed and uncompressed sizes are equal is stored raw).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Root (PACx402)&#039;&#039;&#039;: a 0x30-byte header whose sizes at 0x10 to 0x24 are, in order, the node trees, the dependency section, the data entries, the string table, the file data and the offset table (0x0C is the total size).&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Dependency section&#039;&#039;&#039;: &amp;lt;code&amp;gt;u64 count&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 → entries&amp;lt;/code&amp;gt;. Each 0x20-byte entry holds &amp;lt;code&amp;gt;u64 → name&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u32 compressed size&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u32 size&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u32 offset of the block in the outer file&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u32 chunk count&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;u64 → chunk table&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;{u32 compressed, u32 size}&amp;lt;/code&amp;gt; per chunk). &#039;&#039;&#039;Each block&#039;s chunk table lives in the root&#039;&#039;&#039;, so a block that grows needs more table entries in the root.&lt;br /&gt;
* &#039;&#039;&#039;Data entries&#039;&#039;&#039; (0x30 bytes): &amp;lt;code&amp;gt;u32 uid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u32 size&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 0&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 → data&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 0&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 → extension&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 flags&amp;lt;/code&amp;gt;. A file stored in a block is listed in the root with flags 1 and data pointer 0, and the block (itself a PACx402 archive, without a dependency section) holds its data.&lt;br /&gt;
* &#039;&#039;&#039;Names&#039;&#039;&#039; are radix trees of fragments, as documented by HedgeLib&#039;s PACx v4 code. Each node is 0x28 bytes: &amp;lt;code&amp;gt;u64 → fragment&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 → data&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 → child indices&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i32 parent&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i32 global index&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i32 data index&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u16 child count&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u8 has data&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u8 full path size&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;Offset table&#039;&#039;&#039;: the position of every pointer, as deltas &amp;gt;&amp;gt; 2 in 1, 2 or 4 bytes (top bits &amp;lt;code&amp;gt;01&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;10&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;11&amp;lt;/code&amp;gt;), padded with zeros to 8. With it, bytes can be inserted anywhere and every pointer fixed up.&lt;br /&gt;
&lt;br /&gt;
Rebuilding an archive with nothing changed reproduces the original byte for byte, provided unchanged 64 KB chunks are written back as their original compressed bytes.&lt;br /&gt;
&lt;br /&gt;
=== The scene: main_menu_chara_select ===&lt;br /&gt;
&amp;lt;code&amp;gt;menu_main_menu_chara_select.swif&amp;lt;/code&amp;gt; is a SurfRide &#039;&#039;&#039;SWIF&#039;&#039;&#039; file (the &amp;quot;version 5&amp;quot; layout used by Origins; structures as in DeaTh-G&#039;s surfboard templates). All pointers are u64 absolute file offsets, 8-aligned, and listed in the &amp;lt;code&amp;gt;SOF0&amp;lt;/code&amp;gt; chunk. Relevant layers:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;lay&amp;lt;/code&amp;gt;: the window. Its card casts are &amp;lt;code&amp;gt;obj_btn_1&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;obj_btn_7&amp;lt;/code&amp;gt; (each inside &amp;lt;code&amp;gt;lay_btn_N&amp;lt;/code&amp;gt;). The save line under the cards is &amp;lt;code&amp;gt;null_save_stage&amp;lt;/code&amp;gt; with the text &amp;lt;code&amp;gt;sysf_save_stage&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;ref_btn&amp;lt;/code&amp;gt;: the card template that every &amp;lt;code&amp;gt;obj_btn_N&amp;lt;/code&amp;gt; instances. It holds the picture groups &amp;lt;code&amp;gt;null_char_1&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;null_char_7&amp;lt;/code&amp;gt; (one per kind, for example &amp;lt;code&amp;gt;null_char_1&amp;lt;/code&amp;gt; containing &amp;lt;code&amp;gt;sonic_1&amp;lt;/code&amp;gt;), the name texts &amp;lt;code&amp;gt;sysf_btn_name_1&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;_2&amp;lt;/code&amp;gt;, and the save icon. Its animations are the shared states (&amp;lt;code&amp;gt;in&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;loop&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;out&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;over&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;loop_act&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;go_away&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;press&amp;lt;/code&amp;gt;), then &amp;lt;code&amp;gt;PRM_chara_1&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;PRM_chara_7&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;PRM_save_icon_on&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;PRM_save_icon_off&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;How a card&#039;s picture works:&#039;&#039;&#039; &amp;lt;code&amp;gt;PRM_chara_&amp;amp;#x3C;kind+1&amp;amp;#x3E;&amp;lt;/code&amp;gt; only switches the &amp;lt;code&amp;gt;Display&amp;lt;/code&amp;gt; of the groups &amp;lt;code&amp;gt;null_char_1&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;_7&amp;lt;/code&amp;gt; (and of &amp;lt;code&amp;gt;sysf_btn_name_2&amp;lt;/code&amp;gt;). The shared state animations key the groups&#039; sprite frames, but never touch a group they don&#039;t know. A new group&#039;s &amp;lt;code&amp;gt;Display&amp;lt;/code&amp;gt; and crop index therefore stay as its pattern set them.&lt;br /&gt;
&lt;br /&gt;
The card pictures are BC7 textures. &amp;lt;code&amp;gt;menu_main_menu_chara_select_chara_sonic.dds&amp;lt;/code&amp;gt; is 2640×1320 (BC7, DXGI format 98, one mip). The &amp;lt;code&amp;gt;.swif&amp;lt;/code&amp;gt; declares textures at &#039;&#039;&#039;half&#039;&#039;&#039; their DDS size (1320×660 here), and crops are stored as normalised &amp;lt;code&amp;gt;(left, top, right, bottom)&amp;lt;/code&amp;gt; floats.&lt;br /&gt;
&lt;br /&gt;
=== Text keys ===&lt;br /&gt;
Names come from &amp;lt;code&amp;gt;image/x64/raw/text/text_menu_&amp;amp;#x3C;language&amp;amp;#x3E;.pac&amp;lt;/code&amp;gt;. These are small PACx403 archives whose content is all in the root, holding a &#039;&#039;&#039;cnvrs-text&#039;&#039;&#039; file (BINA 2.1):&lt;br /&gt;
&lt;br /&gt;
* root: &amp;lt;code&amp;gt;u8 version (6)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u8 languages (1)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u16 entry count&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u32 0&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 → entries&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 → language name&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 0&amp;lt;/code&amp;gt;;&lt;br /&gt;
* entries (0x30 bytes each, &#039;&#039;&#039;sorted by id&#039;&#039;&#039;): &amp;lt;code&amp;gt;u64 id&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 → key&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 → attributes&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 → UTF-16 text&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 length&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;u64 0&amp;lt;/code&amp;gt;;&lt;br /&gt;
* the id is a hash of the key, &amp;lt;code&amp;gt;h = h * 127 + byte&amp;lt;/code&amp;gt; (mod 2&amp;lt;sup&amp;gt;32&amp;lt;/sup&amp;gt;), and the game looks keys up by id.&lt;br /&gt;
&lt;br /&gt;
== Adding cards ==&lt;br /&gt;
This approach is confirmed working in game. It has two halves: &#039;&#039;&#039;archive edits&#039;&#039;&#039; made once at build time, and &#039;&#039;&#039;runtime patches&#039;&#039;&#039; from a mod DLL (loaded by HiteModLoader through HedgeModManager; hooks via MinHook or similar).&lt;br /&gt;
&lt;br /&gt;
=== Archive edits (64 generic card slots) ===&lt;br /&gt;
Instead of one picture group per new character, which would need a hide track per group in every animation, the scene gets &#039;&#039;&#039;one generic group whose crop index picks the picture&#039;&#039;&#039;. The steps below are done in both &amp;lt;code&amp;gt;ui_gamestage.pac&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ui_mainmenu.pac&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;&#039;Texture:&#039;&#039;&#039; append rows of cells (for example 64 cells of 440×536 DDS pixels, filled with transparent BC7 blocks) below &amp;lt;code&amp;gt;menu_main_menu_chara_select_chara_sonic.dds&amp;lt;/code&amp;gt; in its dependency block. Update the DDS height, the file&#039;s size in the block and in the root&#039;s entry, and the block&#039;s chunk table in the root.&lt;br /&gt;
# &#039;&#039;&#039;Crops:&#039;&#039;&#039; in the &amp;lt;code&amp;gt;.swif&amp;lt;/code&amp;gt;&#039;s texture list, rescale that texture&#039;s existing crops to the taller texture (the same pixels). Add one crop per cell, and set the texture&#039;s declared height (half the DDS height) and crop count.&lt;br /&gt;
# &#039;&#039;&#039;Casts&#039;&#039;&#039; (layer &amp;lt;code&amp;gt;ref_btn&amp;lt;/code&amp;gt;): add a group &amp;lt;code&amp;gt;null_char_extra&amp;lt;/code&amp;gt; as the next sibling of the game&#039;s last &amp;lt;code&amp;gt;null_char_N&amp;lt;/code&amp;gt;. Give it a child image cast &amp;lt;code&amp;gt;chara_extra&amp;lt;/code&amp;gt;, copied from Sonic&#039;s &amp;lt;code&amp;gt;sonic_1&amp;lt;/code&amp;gt; but with the cell&#039;s size and &#039;&#039;&#039;one crop reference per slot&#039;&#039;&#039; (&amp;lt;code&amp;gt;{u16 texture list, u16 texture, u16 crop}&amp;lt;/code&amp;gt;, crop index 0 = slot 1).&lt;br /&gt;
# &#039;&#039;&#039;Animations&#039;&#039;&#039; (layer &amp;lt;code&amp;gt;ref_btn&amp;lt;/code&amp;gt;): for each slot &#039;&#039;s&#039;&#039;, add &amp;lt;code&amp;gt;PRM_extra_&amp;amp;#x3C;s&amp;amp;#x3E;&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;PRM_extra_&amp;amp;#x3C;s&amp;amp;#x3E;_2&amp;lt;/code&amp;gt;, copied from &amp;lt;code&amp;gt;PRM_chara_1&amp;lt;/code&amp;gt;&#039;s header. Each has constant keys at frame 0: &amp;lt;code&amp;gt;Display = 0&amp;lt;/code&amp;gt; on &amp;lt;code&amp;gt;null_char_1&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;_7&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Display = 1&amp;lt;/code&amp;gt; on &amp;lt;code&amp;gt;null_char_extra&amp;lt;/code&amp;gt;, &#039;&#039;&#039;&amp;lt;code&amp;gt;CropIndex0 = s − 1&amp;lt;/code&amp;gt;&#039;&#039;&#039; on &amp;lt;code&amp;gt;chara_extra&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;Display&amp;lt;/code&amp;gt; of &amp;lt;code&amp;gt;sysf_btn_name_2&amp;lt;/code&amp;gt; set to 1 only in the &amp;lt;code&amp;gt;_2&amp;lt;/code&amp;gt; variant.&lt;br /&gt;
# &#039;&#039;&#039;Hide the new group in the game&#039;s patterns:&#039;&#039;&#039; add &amp;lt;code&amp;gt;Display = 0&amp;lt;/code&amp;gt; on &amp;lt;code&amp;gt;null_char_extra&amp;lt;/code&amp;gt; to each of &amp;lt;code&amp;gt;PRM_chara_1&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;_7&amp;lt;/code&amp;gt;.&lt;br /&gt;
# &#039;&#039;&#039;Text:&#039;&#039;&#039; in every &amp;lt;code&amp;gt;text_menu_&amp;amp;#x3C;language&amp;amp;#x3E;.pac&amp;lt;/code&amp;gt;, add the keys &amp;lt;code&amp;gt;MAINMENU_character_name_extra&amp;amp;#x3C;s&amp;amp;#x3E;_1&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;_2&amp;lt;/code&amp;gt; for each slot, with room for 31 UTF-16 units each. Re-sort the entries by id and check that no two keys share a hash.&lt;br /&gt;
&lt;br /&gt;
All edits are &#039;&#039;&#039;append-only&#039;&#039;&#039;: new structures go after the last chunk (before &amp;lt;code&amp;gt;SOF0&amp;lt;/code&amp;gt;), existing pointer fields are repointed at them, and the new pointers are added to &amp;lt;code&amp;gt;SOF0&amp;lt;/code&amp;gt;. Nothing existing moves. Done this way, the archives rebuild byte-identically when no slots are added.&lt;br /&gt;
&lt;br /&gt;
The same idea gives the CONTINUE head its new characters. In &amp;lt;code&amp;gt;ui_mainmenu.pac&amp;lt;/code&amp;gt;, append 120×96 cells below &amp;lt;code&amp;gt;menu_main_menu_icon_chara_s.dds&amp;lt;/code&amp;gt;. In each language&#039;s &amp;lt;code&amp;gt;menu_main_menu_island.swif&amp;lt;/code&amp;gt;, rescale the four existing crops and add one crop per cell (crop &amp;lt;code&amp;gt;4 + slot − 1&amp;lt;/code&amp;gt;). Then give both &amp;lt;code&amp;gt;pattern_chara_1&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;_2&amp;lt;/code&amp;gt; a crop reference for every crop.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Filling slots without rebuilding:&#039;&#039;&#039; store the 64 KB chunks that hold the cells and name texts as &#039;&#039;&#039;LZ4 blocks of literals only&#039;&#039;&#039;, so those bytes sit in the file uncompressed. A descriptor (&amp;lt;code&amp;gt;raw/ui/cards.json&amp;lt;/code&amp;gt;) records where those runs, each slot&#039;s cell rows and each name&#039;s text and length are. At startup the DLL writes each installed character&#039;s picture and name into a cached copy of each archive and serves the copies. The game archives are never modified.&lt;br /&gt;
&lt;br /&gt;
=== Runtime patches ===&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;&#039;Kind table:&#039;&#039;&#039; copy the 7 vanilla entries into memory allocated within ±2 GB of the executable, so rip-relative references reach it. Add one entry per new kind (7 and up), with the pattern &amp;lt;code&amp;gt;PRM_extra_&amp;amp;#x3C;slot&amp;amp;#x3E;&amp;lt;/code&amp;gt; (or &amp;lt;code&amp;gt;_2&amp;lt;/code&amp;gt;) and the name keys &amp;lt;code&amp;gt;MAINMENU_character_name_extra&amp;amp;#x3C;slot&amp;amp;#x3E;_1&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;_2&amp;lt;/code&amp;gt;. Repoint the three &amp;lt;code&amp;gt;lea&amp;lt;/code&amp;gt;s in the build function at the copy.&lt;br /&gt;
# &#039;&#039;&#039;Range checks:&#039;&#039;&#039; redirect the six &amp;quot;kind ≤ 6&amp;quot; call sites (five in the build function, one at &amp;lt;code&amp;gt;0x1403E0F10 + 0x47&amp;lt;/code&amp;gt;) to a stub returning &amp;quot;kind ≤ highest kind&amp;quot; (&amp;lt;code&amp;gt;cmp cl, imm8 / setbe al / ret&amp;lt;/code&amp;gt;).&lt;br /&gt;
# &#039;&#039;&#039;Second name line:&#039;&#039;&#039; redirect the &amp;lt;code&amp;gt;KindHasTails&amp;lt;/code&amp;gt; call at build &amp;lt;code&amp;gt;+0xAB1&amp;lt;/code&amp;gt; so new kinds can show two lines.&lt;br /&gt;
# &#039;&#039;&#039;Rows:&#039;&#039;&#039; hook &amp;lt;code&amp;gt;RowPicker&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;0x1403E0D80&amp;lt;/code&amp;gt;) and return your own 7-byte row with the new kinds included. Skip game 2 (S3&amp;amp;K).&lt;br /&gt;
# &#039;&#039;&#039;Launch:&#039;&#039;&#039; hook &amp;lt;code&amp;gt;KindAllowedInGame&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;0x140331080&amp;lt;/code&amp;gt;) to accept the new kinds outside S3&amp;amp;K. Without this, they start as Sonic &amp;amp; Tails.&lt;br /&gt;
# &#039;&#039;&#039;Saves:&#039;&#039;&#039; hook &amp;lt;code&amp;gt;SlotIndex&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;0x14045DA50&amp;lt;/code&amp;gt;) to give new kinds Sonic&#039;s slot, so callers get a valid record and don&#039;t crash. Then wrap the four views so a new kind&#039;s record pointer is swapped for a buffer of your own after the original fills &amp;lt;code&amp;gt;out&amp;lt;/code&amp;gt;. Origins&#039; own save file is never written with a new kind.&lt;br /&gt;
# &#039;&#039;&#039;Last kind:&#039;&#039;&#039; hook &amp;lt;code&amp;gt;SetLastKind&amp;lt;/code&amp;gt; (&amp;lt;code&amp;gt;0x14045CB50&amp;lt;/code&amp;gt;) so it never stores a kind above 6 in Origins&#039; save (store 0 instead), and remember the real one yourself.&lt;br /&gt;
# &#039;&#039;&#039;More than 7 cards:&#039;&#039;&#039; hook &amp;lt;code&amp;gt;NextEnabledIndex&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;PrevEnabledIndex&amp;lt;/code&amp;gt; as described in [[Sonic Origins Character Select#Paging (NextEnabledIndex / PrevEnabledIndex)|Paging]], plus the build function to reset the page.&lt;br /&gt;
# &#039;&#039;&#039;CONTINUE head (optional):&#039;&#039;&#039; redirect the four &amp;lt;code&amp;gt;GetLastKind&amp;lt;/code&amp;gt; call sites (&amp;lt;code&amp;gt;0x1403E509D&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x1403E92AD&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x1403546D9&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x14033A422&amp;lt;/code&amp;gt;) to return your remembered kind when Origins says 0. Let it through the row check at &amp;lt;code&amp;gt;0x140354705&amp;lt;/code&amp;gt;. Wrap the two &amp;lt;code&amp;gt;SetIconChara&amp;lt;/code&amp;gt; calls (&amp;lt;code&amp;gt;0x1403E8436&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x1403E94D3&amp;lt;/code&amp;gt;): draw Sonic&#039;s head, then &amp;lt;code&amp;gt;SetCropIndex(layer, &amp;quot;pattern_chara_1&amp;quot;, 4 + slot − 1)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Patch only after checking every function&#039;s first bytes and every call site&#039;s &amp;lt;code&amp;gt;E8 rel32&amp;lt;/code&amp;gt; target. If anything differs, patch nothing.&lt;br /&gt;
&lt;br /&gt;
=== Re-finding the functions ===&lt;br /&gt;
These are the exact bytes to compare before hooking (at the start of each function, unless noted). Search the executable for them after an update.&lt;br /&gt;
{| class=&amp;quot;mw-collapsible wikitable&amp;quot; style=&amp;quot;margin: auto;&amp;quot; width=&amp;quot;90%&amp;quot;&lt;br /&gt;
!Address&lt;br /&gt;
!Function&lt;br /&gt;
!Bytes&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x1403E0D80&amp;lt;/code&amp;gt;&lt;br /&gt;
|RowPicker&lt;br /&gt;
|&amp;lt;code&amp;gt;48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 41 56 41 57 48 83 EC 20&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x14045DA50&amp;lt;/code&amp;gt;&lt;br /&gt;
|SlotIndex&lt;br /&gt;
|&amp;lt;code&amp;gt;48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 48 83 EC 20 0F B6 E9&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x14045CB50&amp;lt;/code&amp;gt;&lt;br /&gt;
|SetLastKind&lt;br /&gt;
|&amp;lt;code&amp;gt;44 0F BE D2 4C 8B D9 84 D2 74 3C&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140331080&amp;lt;/code&amp;gt;&lt;br /&gt;
|KindAllowedInGame&lt;br /&gt;
|&amp;lt;code&amp;gt;80 F9 06 77 2A 0F BE D2 85 D2 74 1C&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x1403BFBB0&amp;lt;/code&amp;gt;&lt;br /&gt;
|NextEnabledIndex&lt;br /&gt;
|&amp;lt;code&amp;gt;48 89 5C 24 08 48 89 7C 24 10 4C 8B 99 F8 02 00 00 45 33 C9&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x1403BFC40&amp;lt;/code&amp;gt;&lt;br /&gt;
|PrevEnabledIndex&lt;br /&gt;
|&amp;lt;code&amp;gt;48 89 5C 24 08 48 89 7C 24 10 4C 8B 99 F8 02 00 00 45 33 C0&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x1403C0810&amp;lt;/code&amp;gt;&lt;br /&gt;
|MenuItemContainer::Update&lt;br /&gt;
|&amp;lt;code&amp;gt;48 89 5C 24 20 57 48 83 EC 40 8B 81 18 03 00 00&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x1403E0030&amp;lt;/code&amp;gt;&lt;br /&gt;
|Popup build (vtable slot 6)&lt;br /&gt;
|&amp;lt;code&amp;gt;48 89 54 24 10 55 53 56 57 41 54 41 55 41 56 41 57 48 8D AC 24 78 F2 FF&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140459B60&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x14045A190&amp;lt;/code&amp;gt;&lt;br /&gt;
|Save views (0x10, 0x10001)&lt;br /&gt;
|&amp;lt;code&amp;gt;48 89 5C 24 08 48 89 74 24 10 57 48 83 EC 20 41 0F B6 C0 48 8B DA&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140459420&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x140459360&amp;lt;/code&amp;gt;&lt;br /&gt;
|Save views (0xA0, 0x8000)&lt;br /&gt;
|&amp;lt;code&amp;gt;48 89 5C 24 08 57 48 83 EC 20 41 0F B6 C0 48 8B DA&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x14045A110&amp;lt;/code&amp;gt;&lt;br /&gt;
|GetLastKind&lt;br /&gt;
|&amp;lt;code&amp;gt;48 0F BE C2 4C 8B C9 83 F8 07 77 4B&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x1403E0FA0&amp;lt;/code&amp;gt;&lt;br /&gt;
|RowHasKind&lt;br /&gt;
|&amp;lt;code&amp;gt;40 53 48 83 EC 20 41 0F B6 C1 41 0F B6 D8&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x1403E7830&amp;lt;/code&amp;gt;&lt;br /&gt;
|SetIconChara&lt;br /&gt;
|&amp;lt;code&amp;gt;48 85 D2 0F 84 8D 00 00 00 48 89 5C 24 08 57 48 83 EC 20 41 0F B6 C8&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140483A00&amp;lt;/code&amp;gt;&lt;br /&gt;
|SetCropIndex&lt;br /&gt;
|&amp;lt;code&amp;gt;40 53 48 83 EC 20 41 8B D8 E8&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x1403E1140&amp;lt;/code&amp;gt;&lt;br /&gt;
|UpdateSaveInfo&lt;br /&gt;
|&amp;lt;code&amp;gt;40 55 53 41 57 48 8D AC 24 40 FF FF FF 48 81 EC C0 01 00 00 8B DA&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140D59518&amp;lt;/code&amp;gt;&lt;br /&gt;
|Head crop table (data)&lt;br /&gt;
|&amp;lt;code&amp;gt;00 00 00 00 FF FF FF FF&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0x140D58748&amp;lt;/code&amp;gt;&lt;br /&gt;
|Sonic 1 row table (data)&lt;br /&gt;
|The 49 bytes listed in [[Sonic Origins Character Select#The row tables|The row tables]]&lt;br /&gt;
|}&lt;br /&gt;
The call sites are checked as an &amp;lt;code&amp;gt;E8&amp;lt;/code&amp;gt; opcode whose &amp;lt;code&amp;gt;rel32&amp;lt;/code&amp;gt; lands on the expected function. The &amp;lt;code&amp;gt;lea&amp;lt;/code&amp;gt; sites are checked as &amp;lt;code&amp;gt;48/4C 8D&amp;lt;/code&amp;gt; with a rip-relative ModRM whose target is the kind table.&lt;br /&gt;
&lt;br /&gt;
== Saves and pitfalls ==&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Per-character records:&#039;&#039;&#039; give each new character its own record buffers for all four views, kept in a file of your own (for example under &amp;lt;code&amp;gt;%APPDATA%\SEGA\SonicOrigins\&amp;lt;/code&amp;gt;), never in Origins&#039; save.&lt;br /&gt;
* &#039;&#039;&#039;CONTINUE loaded the wrong character before per-kind saves:&#039;&#039;&#039; while new kinds shared Sonic&#039;s record, &amp;quot;continue&amp;quot; on an extra&#039;s card restored whichever character was stored in Sonic&#039;s shared record (picking Metal Sonic loaded Big). New games were fine. Separate records fixed it.&lt;br /&gt;
* &#039;&#039;&#039;Kinds 4 and 6 in Sonic 1:&#039;&#039;&#039; putting Sonic 2&#039;s full row (&amp;lt;code&amp;gt;0 1 2 5 3 4 6&amp;lt;/code&amp;gt;) into Sonic 1&#039;s table crashed when the popup opened. Sonic 1 appears to have no setup for kind 4 or 6 (kind 3 works in Sonic 1: picture, name and launch as Sonic &amp;amp; Tails).&lt;br /&gt;
* &#039;&#039;&#039;The 7-card window:&#039;&#039;&#039; the layout has exactly 7 card casts and the rows are 7 wide in code, so more characters need paging (above), not wider rows.&lt;br /&gt;
* &#039;&#039;&#039;Kinds 7 and up with no save slot:&#039;&#039;&#039; &amp;lt;code&amp;gt;SlotIndex&amp;lt;/code&amp;gt; returns no slot, and callers crash on the null record. Map them to a vanilla slot first, then swap the record.&lt;br /&gt;
* &#039;&#039;&#039;Never send a new kind to Origins:&#039;&#039;&#039; the scripts&#039; &amp;lt;code&amp;gt;NOTIFY_PLAYER_SET&amp;lt;/code&amp;gt; callback with an unknown ID (7) crashes the game. With an extra&#039;s ID in &amp;lt;code&amp;gt;stage.playerListPos&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;NOTIFY_SPECIAL_RETRY&amp;lt;/code&amp;gt; (special stage results in Sonic 1 and 2) left the results screen blank or showing garbage. Report the character as Sonic for these calls.&lt;br /&gt;
* &#039;&#039;&#039;Which archive:&#039;&#039;&#039; edits to &amp;lt;code&amp;gt;ui_mainmenu.pac&amp;lt;/code&amp;gt; alone don&#039;t show in the popup. Use &amp;lt;code&amp;gt;ui_gamestage.pac&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Credits and references ==&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;NoSwap&#039;&#039;&#039; (source to be published). The references for everything above are its tools and DLL headers:&lt;br /&gt;
** &amp;lt;code&amp;gt;tools/origins_pacx.py&amp;lt;/code&amp;gt;: split PACx403/402 reader and writer, with pointer relocation;&lt;br /&gt;
** &amp;lt;code&amp;gt;tools/origins_pac.py&amp;lt;/code&amp;gt;: small unsplit archives and LZ4;&lt;br /&gt;
** &amp;lt;code&amp;gt;tools/surfride.py&amp;lt;/code&amp;gt;: SWIF reader and append-only editor;&lt;br /&gt;
** &amp;lt;code&amp;gt;tools/bc7.py&amp;lt;/code&amp;gt;: exact solid-block BC7;&lt;br /&gt;
** &amp;lt;code&amp;gt;tools/cnvrs_text.py&amp;lt;/code&amp;gt;: cnvrs-text;&lt;br /&gt;
** &amp;lt;code&amp;gt;tools/origins_cards.py&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;tools/build_origins_menu.py&amp;lt;/code&amp;gt;: the card slots;&lt;br /&gt;
** &amp;lt;code&amp;gt;native/src/NoSwapS3K.cpp&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;native/src/MenuCards.h&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;native/src/Roster.h&amp;lt;/code&amp;gt;: the hooks, card writing and kind registry.&lt;br /&gt;
* &#039;&#039;&#039;HedgeLib&#039;&#039;&#039; by Radfordhound: the PACx v4 layout (node trees, data entries).&lt;br /&gt;
* &#039;&#039;&#039;HedgeArcPack&#039;&#039;&#039; (HedgeLib): the standard tool for these archives.&lt;br /&gt;
* &#039;&#039;&#039;HedgeModManager&#039;&#039;&#039; and &#039;&#039;&#039;HiteModLoader&#039;&#039;&#039; (hedge-dev): loading code and file mods into Origins.&lt;br /&gt;
* &#039;&#039;&#039;DeaTh-G&#039;s surfboard templates&#039;&#039;&#039;: the SurfRide SWIF structures.&lt;br /&gt;
* &#039;&#039;&#039;The RSDK decompilation projects&#039;&#039;&#039; by Rubberduckycooly and chuliRMG (RSDKv3, RSDKv4 and RSDKv5(U); original Retro Engine by Christian Whitehead / Evening Star), and the script decompilations: character IDs and engine behaviour on the classic-game side.&lt;br /&gt;
* &#039;&#039;&#039;Origins Ultrafix&#039;&#039;&#039;: its &amp;lt;code&amp;gt;Ultrafix3kFixes&amp;lt;/code&amp;gt; headers were the reference for RSDK and S3&amp;amp;K types and some fixed addresses.&lt;br /&gt;
* &#039;&#039;&#039;MinHook&#039;&#039;&#039; by Tsuda Kageyu: function hooking.&lt;/div&gt;</summary>
		<author><name>Superevil</name></author>
	</entry>
</feed>