2017-12-28 16:28:23 +00:00
# Design Flaws
2017-12-28 17:14:47 +00:00
These are parts of the code that do not work *incorrectly* , like [bugs and glitches ](/docs/bugs_and_glitches.md ), but that clearly exist just to work around a problem. In other words, with a slightly different design, the code would not need to exist at all. Design flaws may be exceptions to a usual rule, such as "tables of pointers in different banks use `dba` " ([one exception](#pic-banks-are-offset-by-pics_fix), [and another ](#pokédex-entry-banks-are-derived-from-their-species-ids )) or "graphics used as a unit are stored and loaded contiguously" ([a notable exception](#footprints-are-split-into-top-and-bottom-halves)).
2017-12-28 16:50:02 +00:00
2017-12-28 16:28:23 +00:00
## Contents
- [Pic banks are offset by `PICS_FIX` ](#pic-banks-are-offset-by-pics_fix )
- [`PokemonPicPointers` and `UnownPicPointers` are assumed to start at the same address ](#pokemonpicpointers-and-unownpicpointers-are-assumed-to-start-at-the-same-address )
- [Footprints are split into top and bottom halves ](#footprints-are-split-into-top-and-bottom-halves )
- [`ITEM_C3` and `ITEM_DC` break up the continuous sequence of TM items ](#item_c3-and-item_dc-break-up-the-continuous-sequence-of-tm-items )
2018-02-04 01:40:21 +00:00
- [Pokédex entry banks are derived from their species IDs ](#pokédex-entry-banks-are-derived-from-their-species-ids )
- [Identical sine wave code and data is repeated five times ](#identical-sine-wave-code-and-data-is-repeated-five-times )
2017-12-28 17:49:32 +00:00
- [`GetForestTreeFrame` works, but it's still bad ](#getforesttreeframe-works-but-its-still-bad )
2017-12-28 16:28:23 +00:00
## Pic banks are offset by `PICS_FIX`
[data/pokemon/pic_pointers.asm ](/data/pokemon/pic_pointers.asm ), [data/pokemon/unown_pic_pointers.asm ](/data/pokemon/unown_pic_pointers.asm ), and [data/trainers/pic_pointers.asm ](/data/trainers/pic_pointers.asm ) all have to use `dba_pic` instead of `dba` . This is a macro in [macros/data.asm ](/macros/data.asm ) that offsets banks by `PICS_FIX` :
```asm
dba_pic: MACRO ; dbw bank, address
db BANK(\1) - PICS_FIX
dw \1
ENDM
```
2018-04-01 15:01:51 +00:00
The offset is translated into a correct bank by `FixPicBank` in [engine/gfx/load_pics.asm ](/engine/gfx/load_pics.asm ):
2017-12-28 16:28:23 +00:00
```asm
FixPicBank: ; 511c5
; This is a thing for some reason.
PICS_FIX EQU $36
GLOBAL PICS_FIX
push hl
push bc
2018-06-04 03:18:31 +00:00
sub BANK("Pics 1") - PICS_FIX
2017-12-28 16:28:23 +00:00
ld c, a
ld b, 0
ld hl, .PicsBanks
add hl, bc
ld a, [hl]
pop bc
pop hl
ret
.PicsBanks: ; 511d4
2018-06-04 03:18:31 +00:00
db BANK("Pics 1") ; BANK("Pics 1") + 0
db BANK("Pics 2") ; BANK("Pics 1") + 1
db BANK("Pics 3") ; BANK("Pics 1") + 2
db BANK("Pics 4") ; BANK("Pics 1") + 3
db BANK("Pics 5") ; BANK("Pics 1") + 4
db BANK("Pics 6") ; BANK("Pics 1") + 5
db BANK("Pics 7") ; BANK("Pics 1") + 6
db BANK("Pics 8") ; BANK("Pics 1") + 7
db BANK("Pics 9") ; BANK("Pics 1") + 8
db BANK("Pics 10") ; BANK("Pics 1") + 9
db BANK("Pics 11") ; BANK("Pics 1") + 10
db BANK("Pics 12") ; BANK("Pics 1") + 11
db BANK("Pics 13") ; BANK("Pics 1") + 12
db BANK("Pics 14") ; BANK("Pics 1") + 13
db BANK("Pics 15") ; BANK("Pics 1") + 14
db BANK("Pics 16") ; BANK("Pics 1") + 15
db BANK("Pics 17") ; BANK("Pics 1") + 16
db BANK("Pics 18") ; BANK("Pics 1") + 17
db BANK("Pics 19") ; BANK("Pics 1") + 18
2018-06-04 03:25:21 +00:00
db BANK("Pics 20") ; BANK("Pics 1") + 19
db BANK("Pics 21") ; BANK("Pics 1") + 20
db BANK("Pics 22") ; BANK("Pics 1") + 21
db BANK("Pics 23") ; BANK("Pics 1") + 22
db BANK("Pics 24") ; BANK("Pics 1") + 23
2017-12-28 16:28:23 +00:00
```
2018-06-04 03:18:31 +00:00
**Fix:** Use `dba` instead of `dba_pic` , delete `FixPicBank` , and remove all four calls to `FixPicBank` .
2017-12-28 17:49:32 +00:00
2017-12-28 16:28:23 +00:00
## `PokemonPicPointers` and `UnownPicPointers` are assumed to start at the same address
In [gfx/pics.asm ](/gfx/pics.asm ):
```asm
; PokemonPicPointers and UnownPicPointers are assumed to start at the same
; address, but in different banks. This is enforced in pokecrystal.link.
SECTION "Pic Pointers", ROMX
INCLUDE "data/pokemon/pic_pointers.asm"
SECTION "Unown Pic Pointers", ROMX
INCLUDE "data/pokemon/unown_pic_pointers.asm"
```
2017-12-28 17:49:32 +00:00
In [pokecrystal.link ](/pokecrystal.link ):
```
ROMX $48
org $4000
"Pic Pointers"
"Pics 1"
ROMX $49
org $4000
"Unown Pic Pointers"
"Pics 2"
```
2018-04-01 15:01:51 +00:00
Two routines in [engine/gfx/load_pics.asm ](/engine/gfx/load_pics.asm ) make this assumption; `GetFrontpicPointer` :
2017-12-28 16:28:23 +00:00
```asm
2018-02-04 01:40:21 +00:00
ld a, [wCurPartySpecies]
2017-12-28 16:28:23 +00:00
cp UNOWN
jr z, .unown
2018-02-04 01:40:21 +00:00
ld a, [wCurPartySpecies]
2017-12-28 16:28:23 +00:00
ld d, BANK(PokemonPicPointers)
jr .ok
.unown
2018-02-04 01:40:21 +00:00
ld a, [wUnownLetter]
2017-12-28 16:28:23 +00:00
ld d, BANK(UnownPicPointers)
.ok
ld hl, PokemonPicPointers ; UnownPicPointers
dec a
ld bc, 6
call AddNTimes
```
And `GetMonBackpic` :
```asm
2018-02-04 01:40:21 +00:00
; These are assumed to be at the same address in their respective banks.
2017-12-28 16:28:23 +00:00
ld hl, PokemonPicPointers ; UnownPicPointers
ld a, b
ld d, BANK(PokemonPicPointers)
cp UNOWN
jr nz, .ok
ld a, c
ld d, BANK(UnownPicPointers)
.ok
dec a
ld bc, 6
call AddNTimes
```
2017-12-28 17:49:32 +00:00
**Fix:**
Don't enforce `org $4000` in pokecrystal.link.
Modify `GetFrontpicPointer` :
```asm
2018-02-04 01:40:21 +00:00
ld a, [wCurPartySpecies]
2017-12-28 17:49:32 +00:00
cp UNOWN
jr z, .unown
2018-02-04 01:40:21 +00:00
ld a, [wCurPartySpecies]
2017-12-28 17:49:32 +00:00
ld hl, PokemonPicPointers
2017-12-28 19:08:29 +00:00
ld d, BANK(PokemonPicPointers)
2017-12-28 17:49:32 +00:00
jr .ok
.unown
2018-02-04 01:40:21 +00:00
ld a, [wUnownLetter]
2017-12-28 17:49:32 +00:00
ld hl, UnownPicPointers
2017-12-28 19:08:29 +00:00
ld d, BANK(UnownPicPointers)
2017-12-28 17:49:32 +00:00
.ok
dec a
ld bc, 6
call AddNTimes
```
And `GetMonBackpic` :
```asm
ld a, b
ld hl, PokemonPicPointers
ld d, BANK(PokemonPicPointers)
cp UNOWN
jr nz, .ok
ld a, c
ld hl, UnownPicPointers
ld d, BANK(UnownPicPointers)
.ok
dec a
ld bc, 6
call AddNTimes
```
2017-12-28 16:28:23 +00:00
## Footprints are split into top and bottom halves
In [gfx/footprints.asm ](/gfx/footprints.asm ):
```asm
; Footprints are 2x2 tiles each, but are stored as a 16x64-tile image
; (32 rows of 8 footprints per row).
; That means there's a row of the top two tiles for eight footprints,
; then a row of the bottom two tiles for those eight footprints.
; These macros help extract the first and the last two tiles, respectively.
footprint_top EQUS "0, 2 * LEN_1BPP_TILE"
footprint_bottom EQUS "2 * LEN_1BPP_TILE, 2 * LEN_1BPP_TILE"
; Entries correspond to Pokémon species, two apiece, 8 tops then 8 bottoms
; 001-008 top halves
INCBIN "gfx/footprints/bulbasaur.1bpp", footprint_top
INCBIN "gfx/footprints/ivysaur.1bpp", footprint_top
INCBIN "gfx/footprints/venusaur.1bpp", footprint_top
INCBIN "gfx/footprints/charmander.1bpp", footprint_top
INCBIN "gfx/footprints/charmeleon.1bpp", footprint_top
INCBIN "gfx/footprints/charizard.1bpp", footprint_top
INCBIN "gfx/footprints/squirtle.1bpp", footprint_top
INCBIN "gfx/footprints/wartortle.1bpp", footprint_top
; 001-008 bottom halves
INCBIN "gfx/footprints/bulbasaur.1bpp", footprint_bottom
INCBIN "gfx/footprints/ivysaur.1bpp", footprint_bottom
INCBIN "gfx/footprints/venusaur.1bpp", footprint_bottom
INCBIN "gfx/footprints/charmander.1bpp", footprint_bottom
INCBIN "gfx/footprints/charmeleon.1bpp", footprint_bottom
INCBIN "gfx/footprints/charizard.1bpp", footprint_bottom
INCBIN "gfx/footprints/squirtle.1bpp", footprint_bottom
INCBIN "gfx/footprints/wartortle.1bpp", footprint_bottom
...
```
2018-01-21 23:00:23 +00:00
`Pokedex_LoadAnyFootprint` in [engine/pokedex/pokedex.asm ](/engine/pokedex/pokedex.asm ):
2017-12-28 16:28:23 +00:00
```asm
push hl
ld e, l
ld d, h
2018-02-04 01:40:21 +00:00
ld hl, vTiles2 tile $62
2017-12-28 16:28:23 +00:00
lb bc, BANK(Footprints), 2
call Request1bpp
pop hl
; Whoever was editing footprints forgot to fix their
; tile editor. Now each bottom half is 8 tiles off.
ld de, 8 tiles
add hl, de
ld e, l
ld d, h
2018-02-04 01:40:21 +00:00
ld hl, vTiles2 tile $64
2017-12-28 16:28:23 +00:00
lb bc, BANK(Footprints), 2
call Request1bpp
```
2017-12-28 17:49:32 +00:00
**Fix:**
Store footprints contiguously:
```asm
INCBIN "gfx/footprints/bulbasaur.1bpp"
INCBIN "gfx/footprints/ivysaur.1bpp"
INCBIN "gfx/footprints/venusaur.1bpp"
INCBIN "gfx/footprints/charmander.1bpp"
INCBIN "gfx/footprints/charmeleon.1bpp"
INCBIN "gfx/footprints/charizard.1bpp"
INCBIN "gfx/footprints/squirtle.1bpp"
INCBIN "gfx/footprints/wartortle.1bpp"
2017-12-28 19:08:29 +00:00
...
2017-12-28 17:49:32 +00:00
```
Modify `Pokedex_LoadAnyFootprint` :
```asm
ld e, l
ld d, h
2018-02-04 01:40:21 +00:00
ld hl, vTiles2 tile $62
2017-12-28 17:49:32 +00:00
lb bc, BANK(Footprints), 4
call Request1bpp
```
2017-12-28 16:28:23 +00:00
2018-02-04 01:40:21 +00:00
## `ITEM_C3` and `ITEM_DC` break up the continuous sequence of TM items
[constants/item_constants.asm ](/constants/item_constants.asm ) defined the 50 TMs in order with `add_tm` , but `ITEM_C3` and `ITEM_DC` break up that sequence.
```asm
add_tm DYNAMICPUNCH ; bf
...
add_tm ROLLOUT ; c2
const ITEM_C3 ; c3
add_tm ROAR ; c4
...
add_tm DIG ; db
const ITEM_DC ; dc
add_tm PSYCHIC_M ; dd
...
add_tm NIGHTMARE ; f2
NUM_TMS = const_value - TM01 - 2 ; discount ITEM_C3 and ITEM_DC
```
2018-04-01 15:01:51 +00:00
`GetTMHMNumber` and `GetNumberedTMHM` in [engine/items/items.asm ](/engine/items/items.asm ) have to compensate for this:
2018-02-04 01:40:21 +00:00
```asm
GetTMHMNumber:: ; d407
; Return the number of a TM/HM by item id c.
ld a, c
; Skip any dummy items.
cp ITEM_C3 ; TM04-05
jr c, .done
cp ITEM_DC ; TM28-29
jr c, .skip
dec a
.skip
dec a
.done
sub TM01
inc a
ld c, a
ret
GetNumberedTMHM: ; d417
; Return the item id of a TM/HM by number c.
ld a, c
; Skip any gaps.
cp ITEM_C3 - (TM01 - 1)
jr c, .done
cp ITEM_DC - (TM01 - 1) - 1
jr c, .skip_one
.skip_two
inc a
.skip_one
inc a
.done
add TM01
dec a
ld c, a
ret
```
**Fix:**
Move `ITEM_C3` and `ITEM_DC` above all the TMs in every table of item data.
2018-04-01 15:01:51 +00:00
Modify engine/items/items.asm:
2018-02-04 01:40:21 +00:00
```asm
GetTMHMNumber:: ; d407
; Return the number of a TM/HM by item id c.
ld a, c
sub TM01
inc a
ld c, a
ret
GetNumberedTMHM: ; d417
; Return the item id of a TM/HM by number c.
ld a, c
add TM01
dec a
ld c, a
ret
```
2017-12-28 16:28:23 +00:00
## Pokédex entry banks are derived from their species IDs
2017-12-28 19:10:46 +00:00
`PokedexDataPointerTable` in [data/pokemon/dex_entry_pointers.asm ](/data/pokemon/dex_entry_pointers.asm ) is a table of `dw` , not `dba` , yet there are four banks used for Pokédex entries. The correct bank is derived from the species ID at the beginning of each Pokémon's base stats. (This is the only use the base stat species ID has.)
2017-12-28 16:28:23 +00:00
2018-01-21 23:00:23 +00:00
Three separate routines do the same derivation; `GetDexEntryPointer` in [engine/pokedex/pokedex_2.asm ](/engine/pokedex/pokedex_2.asm ):
2017-12-28 16:28:23 +00:00
```asm
GetDexEntryPointer: ; 44333
; return dex entry pointer b:de
push hl
ld hl, PokedexDataPointerTable
ld a, b
dec a
ld d, 0
ld e, a
add hl, de
add hl, de
ld e, [hl]
inc hl
ld d, [hl]
push de
rlca
rlca
2018-02-04 01:40:21 +00:00
maskbits NUM_DEX_ENTRY_BANKS
2017-12-28 16:28:23 +00:00
ld hl, .PokedexEntryBanks
ld d, 0
ld e, a
add hl, de
ld b, [hl]
pop de
pop hl
ret
2018-02-04 01:40:21 +00:00
.PokedexEntryBanks:
2017-12-28 16:28:23 +00:00
db BANK(PokedexEntries1)
db BANK(PokedexEntries2)
db BANK(PokedexEntries3)
db BANK(PokedexEntries4)
```
2018-04-01 15:01:51 +00:00
`GetPokedexEntryBank` in [engine/items/item_effects.asm ](/engine/items/item_effects.asm ):
2017-12-28 16:28:23 +00:00
```asm
GetPokedexEntryBank:
push hl
push de
2018-02-04 01:40:21 +00:00
ld a, [wEnemyMonSpecies]
2017-12-28 16:28:23 +00:00
rlca
rlca
2018-02-04 01:40:21 +00:00
maskbits NUM_DEX_ENTRY_BANKS
2017-12-28 16:28:23 +00:00
ld hl, .PokedexEntryBanks
ld d, 0
ld e, a
add hl, de
ld a, [hl]
pop de
pop hl
ret
.PokedexEntryBanks:
db BANK(PokedexEntries1)
db BANK(PokedexEntries2)
db BANK(PokedexEntries3)
db BANK(PokedexEntries4)
```
2018-04-01 15:01:51 +00:00
And `PokedexShow_GetDexEntryBank` in [engine/pokegear/radio.asm ](/engine/pokegear/radio.asm ):
2017-12-28 16:28:23 +00:00
```asm
PokedexShow_GetDexEntryBank:
push hl
push de
2018-02-04 01:40:21 +00:00
ld a, [wCurPartySpecies]
2017-12-28 16:28:23 +00:00
dec a
rlca
rlca
2018-02-04 01:40:21 +00:00
maskbits NUM_DEX_ENTRY_BANKS
ld hl, .PokedexEntryBanks
2017-12-28 16:28:23 +00:00
ld d, 0
ld e, a
add hl, de
ld a, [hl]
pop de
pop hl
ret
2018-02-04 01:40:21 +00:00
.PokedexEntryBanks:
2017-12-28 16:28:23 +00:00
db BANK(PokedexEntries1)
db BANK(PokedexEntries2)
db BANK(PokedexEntries3)
db BANK(PokedexEntries4)
```
2017-12-28 17:49:32 +00:00
**Fix:** Use `dba` instead of `dw` in `PokedexDataPointerTable` , and modify the code that accesses it to match.
2017-12-28 16:28:23 +00:00
2018-02-04 01:40:21 +00:00
## Identical sine wave code and data is repeated five times
2017-12-28 16:28:23 +00:00
2018-04-01 15:01:51 +00:00
`_Sine` in [engine/math/sine.asm ](/engine/math/sine.asm ):
2017-12-28 16:28:23 +00:00
```asm
2018-02-04 01:40:21 +00:00
_Sine:: ; 84d9
2018-02-04 02:11:55 +00:00
; a = d * sin(e * pi/32)
2018-02-04 01:40:21 +00:00
ld a, e
2018-02-04 02:11:55 +00:00
calc_sine_wave
2017-12-28 16:28:23 +00:00
```
2018-04-01 15:01:51 +00:00
`Sprites_Cosine` and `Sprites_Sine` in [engine/gfx/sprites.asm ](/engine/gfx/sprites.asm ):
2017-12-28 16:28:23 +00:00
```asm
2018-02-04 01:40:21 +00:00
Sprites_Cosine: ; 8e72a
; a = d * cos(a * pi/32)
2018-02-04 02:11:55 +00:00
add %010000 ; cos(x) = sin(x + pi/2)
; fallthrough
2018-02-04 01:40:21 +00:00
Sprites_Sine: ; 8e72c
; a = d * sin(a * pi/32)
2018-02-04 02:11:55 +00:00
calc_sine_wave
2018-02-04 01:40:21 +00:00
```
`BattleAnim_Cosine` and `BattleAnim_Sine` in [engine/battle_anims/functions.asm ](/engine/battle_anims/functions.asm ):
```asm
BattleAnim_Cosine: ; ce732 (33:6732)
; a = d * cos(a * pi/32)
2018-02-04 02:11:55 +00:00
add %010000 ; cos(x) = sin(x + pi/2)
; fallthrough
2018-02-04 01:40:21 +00:00
BattleAnim_Sine: ; ce734 (33:6734)
; a = d * sin(a * pi/32)
2018-02-04 02:11:55 +00:00
calc_sine_wave BattleAnimSineWave
2018-02-04 01:40:21 +00:00
...
BattleAnimSineWave: ; ce77f
2018-02-04 02:11:55 +00:00
sine_table 32
2018-02-04 01:40:21 +00:00
; ce7bf
2017-12-28 16:28:23 +00:00
```
2017-12-28 17:49:32 +00:00
2018-02-04 01:40:21 +00:00
`StartTrainerBattle_DrawSineWave` in [engine/battle/battle_transition.asm ](/engine/battle/battle_transition.asm ):
2017-12-28 17:49:32 +00:00
2018-02-04 01:40:21 +00:00
```asm
StartTrainerBattle_DrawSineWave: ; 8c6f7 (23:46f7)
2018-02-04 02:11:55 +00:00
calc_sine_wave
2018-02-04 01:40:21 +00:00
; 8c768
```
And `CelebiEvent_Cosine` in [engine/events/celebi.asm ](/engine/events/celebi.asm ):
2017-12-28 17:49:32 +00:00
```asm
2018-02-04 01:40:21 +00:00
CelebiEvent_Cosine: ; 49b3b (12:5b3b)
; a = d * cos(a * pi/32)
2018-02-04 02:11:55 +00:00
add %010000 ; cos(x) = sin(x + pi/2)
calc_sine_wave
; 49bae
```
They all rely on `calc_sine_wave` in [macros/code.asm ](/macros/code.asm ):
```asm
calc_sine_wave: MACRO
; input: a = a signed 6-bit value
; output: a = d * sin(a * pi/32)
2018-02-04 01:40:21 +00:00
and %111111
2018-02-04 02:11:55 +00:00
cp %100000
jr nc, .negative\@
call .apply\@
2018-02-04 01:40:21 +00:00
ld a, h
ret
2018-02-04 02:11:55 +00:00
.negative\@
2018-02-04 01:40:21 +00:00
and %011111
2018-02-04 02:11:55 +00:00
call .apply\@
2018-02-04 01:40:21 +00:00
ld a, h
xor $ff
2017-12-28 17:49:32 +00:00
inc a
ret
2018-02-04 02:11:55 +00:00
.apply\@
2018-02-04 01:40:21 +00:00
ld e, a
ld a, d
ld d, 0
2018-02-04 02:11:55 +00:00
if _NARG == 1
ld hl, \1
else
ld hl, .sinetable\@
endc
2018-02-04 01:40:21 +00:00
add hl, de
add hl, de
ld e, [hl]
inc hl
ld d, [hl]
ld hl, 0
2018-02-04 02:11:55 +00:00
.multiply\@ ; factor amplitude
2018-02-04 01:40:21 +00:00
srl a
2018-02-04 02:11:55 +00:00
jr nc, .even\@
2018-02-04 01:40:21 +00:00
add hl, de
2018-02-04 02:11:55 +00:00
.even\@
2018-02-04 01:40:21 +00:00
sla e
rl d
and a
2018-02-04 02:11:55 +00:00
jr nz, .multiply\@
2017-12-28 17:49:32 +00:00
ret
2018-02-04 02:11:55 +00:00
if _NARG == 0
.sinetable\@
sine_table 32
endc
ENDM
```
2018-02-04 01:40:21 +00:00
2018-02-04 02:11:55 +00:00
And on `sine_table` in [macros/data.asm ](/macros/data.asm ):
```asm
sine_table: MACRO
; \1 samples of sin(x) from x=0 to x< 32768 ( pi radians )
x = 0
rept \1
dw (sin(x) + (sin(x) & $ff)) >> 8 ; round up
x = x + DIV(32768, \1) ; a circle has 65536 "degrees"
endr
ENDM
2017-12-28 17:49:32 +00:00
```
2018-02-04 02:11:55 +00:00
**Fix:** Edit [home/sine.asm ](/home/sine.asm ) to contain a single copy of the (co)sine code in bank 0, and call it from those five sites.
2018-02-04 01:40:21 +00:00
2017-12-28 17:49:32 +00:00
## `GetForestTreeFrame` works, but it's still bad
2018-04-01 15:01:51 +00:00
In [engine/tilesets/tileset_anims.asm ](/engine/tilesets/tileset_anims.asm ):
2017-12-28 17:49:32 +00:00
```asm
GetForestTreeFrame: ; fc54c
; Return 0 if a is even, or 2 if odd.
and a
jr z, .even
cp 1
jr z, .odd
cp 2
jr z, .even
cp 3
jr z, .odd
cp 4
jr z, .even
cp 5
jr z, .odd
cp 6
jr z, .even
.odd
ld a, 2
scf
ret
.even
xor a
ret
; fc56d
```
**Fix:**
```asm
GetForestTreeFrame: ; fc54c
; Return 0 if a is even, or 2 if odd.
and 1
add a
ret
; fc56d
```