1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
#![warn(missing_docs)]

//! `octopt` is a library for handling CHIP-8 configuration settings.
//!
//! CHIP-8 is a virtual machine for playing simple computer games. It has been around since 1977, and has many slightly incompatible implementations.
//!
//! Games often require specific behavior from its interpreter to run correctly, but you can't know what behavior it expects just by looking at its bytecode.
//!
//! This library contains structs and enums that represent all possible CHIP-8 options, which you can use for your CHIP-8 emulator.

pub mod color;
use color::Color;
mod ini;
use clap::ArgEnum;
use ini::OptionsIni;
use parse_display::{Display, FromStr};
use serde::de::{self, Deserializer, Unexpected};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use serde_with::skip_serializing_none;
use std::fmt;
use std::str::FromStr;
use std::u8;

/// If the CHIP-8 interpreter supports custom colors for visual elements, it can use these values
/// for setting them.
#[skip_serializing_none]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Colors {
    /// The standard color used for active pixels on the CHIP-8 screen. For XO-CHIP, it's used for
    /// the first drawing plane.
    pub fill_color: Option<Color>,
    /// XO-CHIP only: The color used for the second drawing plane.
    pub fill_color2: Option<Color>,
    /// XO-CHIP only: The color used for when both drawing planes overlap.
    pub blend_color: Option<Color>,
    /// The standard background color of the CHIP-8 screen.
    pub background_color: Option<Color>,
    /// The color used by any visual indicator for when the sound buzzer is active.
    pub buzz_color: Option<Color>,
    /// The color used by any visual indicator for when the sound buzzer is inactive.
    pub quiet_color: Option<Color>,
}

/// The default colorscheme here is white on black, which is most common, with non-standard colors
/// for the other elements, albeit inspried by Octo's "Hot Dog" preset.
impl Default for Colors {
    fn default() -> Self {
        Self {
            fill_color: Some(Color {
                r: 255,
                g: 255,
                b: 255,
            }),
            fill_color2: Some(Color {
                r: 255,
                g: 255,
                b: 0,
            }),
            blend_color: Some(Color { r: 255, g: 0, b: 0 }),
            background_color: Some(Color { r: 0, g: 0, b: 0 }),
            buzz_color: Some(Color { r: 153, g: 0, b: 0 }),
            quiet_color: Some(Color { r: 51, g: 0, b: 0 }),
        }
    }
}

/// Represents different CHIP-8 "platforms". In this context, a platform is some CHIP-8 specification
/// which has its own set of [Options]. This includes, but is not limited to, actual target hardware
/// systems that run CHIP-8, specific CHIP-8 interpreters with their own quirks, extensions to the
/// CHIP-8 language, etc.
#[derive(Display, FromStr, Debug, PartialEq, Serialize, Deserialize, Copy, Clone, ArgEnum)]
#[serde(rename_all = "lowercase")]
#[display(style = "lowercase")]
#[non_exhaustive]
pub enum Platform {
    /// The [Octo](https://JohnEarnest.github.io/Octo) interpreter. Corresponds to its "Octo"
    /// compatibility profile.
    Octo,
    /// The original CHIP-8 interpreter implemented on the COSMAC VIP computer.
    Vip,
    /// The CHIPOS/CHIP-8 interpreter implemented on the DREAM-6800 computer.
    Dream6800,
    /// The CHIP-8 interpreter implemented on the ETI-660 computer.
    Eti660,
    /// The CHIP-48 interpreter, which was implemented on the HP 48S calculator.
    Chip48,
    /// The SUPER-CHIP interpreter, which was implemented on the HP 48S calculator.
    Schip,
    /// The XO-CHIP specification, which was implemented in the Octo interpreter.
    XoChip,
}

/// Represents the different touch modes supported by [Octo](https://github.com/JohnEarnest/Octo).
#[derive(Display, FromStr, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[display(style = "lowercase")]
#[non_exhaustive]
pub enum TouchMode {
    /// Do not attempt to handle touch input.
    None,
    /// Taps on the screen are treated like pressing key 6. Swipes or dragging and holding on the
    /// screen are treated like a virtual directional pad based on keys 5,8,7 and 9.
    Swipe,
    /// Treat taps and holds on the center of the screen like an invisible 4x4 hex keypad. Also
    /// supports mouse input.
    Seg16,
    /// The same as Seg16, but the virtual keys take up the entire display, rather than a square
    /// region. Also supports mouse input.
    Seg16Fill,
    /// Draw a translucent virtual gamepad around the screen. The directional pad is mapped to keys
    /// 5,8,7 and 9, and buttons A and B are mapped to keyboard keys 6 and 4, respectively.
    Gamepad,
    /// Display a 4x4 hex keypad under the screen. Also supports mouse input.
    Vip,
}

impl Default for TouchMode {
    fn default() -> Self {
        Self::None
    }
}

/// Represents the different "quirks", ie. divergent behaviors, of the CHIP-8 runtime. These are
/// the most important ones to support, as many games depend on specific settings here to run
/// properly.
///
/// In the following, "original behavior" refers to how the original CHIP-8 interpreter on the
/// COSMAC VIP operated.
///
/// All these quirks are [`Option`]s, because they can be considered to be ternary values. A `Some(true)`
/// value means that the interpreter should use the "quirky" behavior in a particular scenario. A
/// `Some(false)` value means that it should use the "default" behavior. However, a `None` value
/// means that this quirk setting was absent from the metadata, so we don't know what the game
/// requires. This also implies that the interpreter should use some default behavior. This could be
/// either because the game's creator wasn't aware of that particular quirk, or that the program that
/// exported the metadata (usually Octo) wasn't aware of it (probably because it uses the default
/// behavior for that quirk, without any option of configuring it). This is fine, because some of the
/// quirks are obscure, but we still use `Option` in these cases so we don't serialize these quirk
/// settings as `false` when we don't know that the game requires the quirk to be disabled.
///
/// Note that whether a specific behavior is considered "quirky"/"default" or not doesn't necessarily
/// mean that's the original behavior; in many cases, the original behavior is considered "quirky"
/// and requires a `true` value to enable. This is for historical reasons.
///
/// Note also that Octo doesn't support all of these quirks. This struct should support all
/// possible divergent behaviors between widely used CHIP-8 interpreters. A CHIP-8 interpreter
/// should ignore any quirks they don't recognize, or don't have any intention of supporting.
#[skip_serializing_none]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Quirks {
    /// Decides the behavior of the CHIP-8 shift instructions 8XY6 (right shift) and 8XYE (left shift):
    /// * False: The value in the VY register is shifted, and the result is placed in the VX
    /// register. (Original behavior)
    /// * True: The VX register is shifted in-place, and the VY register is ignored. (CHIP48 and
    /// SUPER-CHIP behavior)
    #[serde(
        rename = "shiftQuirks",
        deserialize_with = "some_bool_from_int",
        default
    )]
    pub shift: Option<bool>,
    /// Decides the behavior of the CHIP-8 serialization FX55 (dump registers V0–VX to memory
    /// location I) and FX65 (load registers V0–VX from memory location I):
    /// * False: The value in the I register is incremented for each register loaded/stored.
    /// (Original behavior)
    /// * True: The I register is left unchanged after the operation. (SUPER-CHIP behavior)
    #[serde(
        rename = "loadStoreQuirks",
        deserialize_with = "some_bool_from_int",
        default
    )]
    pub load_store: Option<bool>,
    /// Decides the behavior of the CHIP-8 relative jump instruction BXNN (jump to address XNN,
    /// plus the value in a register):
    /// * False: The value in the V0 register is used for the offset (original behavior)
    /// * True: The value in the VX register is used, where X is the first digit in the target
    /// address XNN (CHIP48 and SUPER-CHIP behavior)
    #[serde(
        rename = "jumpQuirks",
        deserialize_with = "some_bool_from_int",
        default
    )]
    pub jump0: Option<bool>,
    /// Decides the value of the VF flag register after logical instructions 8XY1 (logical OR),
    /// 8XY2 (logical AND) and 8XY3 (logical XOR):
    /// * False: The VF flag register is unchanged by logical instructions (Octo, CHIP48 and
    /// SUPER-CHIP behavior)
    /// * True: The state of the VF flag register is undefined after logical instructions (original
    /// behavior)
    #[serde(
        rename = "logicQuirks",
        deserialize_with = "some_bool_from_int",
        default
    )]
    pub logic: Option<bool>,
    /// Decides the behavior of sprites drawn out of bounds:
    /// * False: Sprites wrap on screen edges (Octo behavior)
    /// * True: Sprites are clipped on screen edges (original, CHIP-48 and SUPER-CHIP behavior)
    #[serde(
        rename = "clipQuirks",
        deserialize_with = "some_bool_from_int",
        default
    )]
    pub clip: Option<bool>,
    /// Decides whether the CHIP-8 interpreter should wait for the rest of the current frame after
    /// each drawing operation:
    /// * False: No special behavior (CHIP-48, SUPER-CHIP and Octo behavior)
    /// * True: After a draw instruction, the CPU does no more work for the rest of the frame, ie.
    /// it waits for a "VBlank interrupt" (original behavior)
    #[serde(
        rename = "vBlankQuirks",
        deserialize_with = "some_bool_from_int",
        default
    )]
    pub vblank: Option<bool>,
    /// Decides whether arithmetic or logical instructions that have the VF register as one of the
    /// operands should set the resulting flag in the VF flag register before or after the value:
    /// * False: The resulting flags are discarded, and the result is placed in the VF register
    /// * True: The resulting value is discarded, and the flag is placed in the VF register
    /// (original behavior)
    #[serde(
        rename = "vfOrderQuirks",
        deserialize_with = "some_bool_from_int",
        default
    )]
    pub vf_order: Option<bool>,
    /// Decides what the behavior of the draw instruction should be if the given sprite height is 0
    /// (DXY0) and the interpreter is in lores (low-resolution 64x32 CHIP-8) mode:
    /// * NoOp: No operation (original behavior)
    /// * TallSprite: Draw a 16-byte sprite (DREAM 6800 behavior)
    /// * BigSprite: Draw a 16x16 pixel sprite, ie. the same behavior as in hires (high-resolution
    /// 128x64 SUPER-CHIP/XO-CHIP) mode (Octo behavior)
    #[serde(rename = "loresDXY0Quirks")]
    pub lores_dxy0: Option<LoResDxy0Behavior>,
    /// Decides whether the screen should be cleared when there is a resolution change (00FE and
    /// 00FF). Note that if this is true, then the screen should retain the current image when
    /// going from lores (low resolution) to hires (high resolution), which implies that the
    /// existing image on the screen should be scaled up 2x.
    /// * True: The screen is cleared if the resolution is changed (Octo behavior)
    /// * False: The screen retains its image if the resolution is changed (original SUPER-CHIP
    /// behavior)
    #[serde(
        rename = "resClearQuirks",
        deserialize_with = "some_bool_from_int",
        default
    )]
    pub res_clear: Option<bool>,
    /// Decides whether the delay timer should wrap around when it has counted down to 0 or not:
    /// * True: The delay timer never stops, but overflows from 0 to 255 and keeps counting (DREAM
    /// 6800 behavior)
    /// * False: The delay timer counts down to 0, and then stops (original behavior)
    #[serde(
        rename = "delayWrapQuirks",
        deserialize_with = "some_bool_from_int",
        default
    )]
    pub delay_wrap: Option<bool>,
    /// Decides the result in the VF flag register when there's a collision of sprites in hires
    /// (high resolution) mode:
    /// * True: VF is set to the number of sprite pixel ros that detected a collision (SUPER-CHIP
    /// 1.1 behavior, hires mode only)
    /// * False: VF is always set to 1 if there is a collision (original behavior)
    #[serde(
        rename = "hiresCollisionQuirks",
        deserialize_with = "some_bool_from_int",
        default
    )]
    pub hires_collision: Option<bool>,
    /// Decides whether sprites clipping at the bottom of the screen should cound as a collision.
    /// Note that this was probably a bug in the SUPER-CHIP 1.1 interpreter, and might not be
    /// required by any games. Also, this doesn't make much sense if `clip_quirks` is false.
    /// * True: VF is set if a sprite runs off the bottom of the screen (SUPER-CHIP 1.1 behavior)
    /// * False: VF is unchanged if a sprite runs off the bottom of the screen (original behavior)
    #[serde(
        rename = "clipCollisionQuirks",
        deserialize_with = "some_bool_from_int",
        default
    )]
    pub clip_collision: Option<bool>,
    /// Decides whether scrolling in lores (low-resolution) mode scrolls by half the number of
    /// pixels as in the high resolution mode. This occured in SUPER-CHIP because the low
    /// resolution display was scaled up 2x; see also the `res_clear` quirk.
    /// * True: In low resolution mode, scrolling left and right will scroll by 2 pixels rather
    /// than 4 (as in high resolution), and scrolling down (and up, with the XO-CHIP instruction)
    /// will scroll by half a pixel, since pixels are scaled upx) (SUPER-CHIP behavior)
    /// * False: Scrolling acts the same in high and low resolution mode (Octo behavior)
    #[serde(
        rename = "scrollQuirks",
        deserialize_with = "some_bool_from_int",
        default
    )]
    pub scroll: Option<bool>,
    /// Decides whether the I address register should set the VF flag register if it "overflows"
    /// from `0x0FFF` to above `0x1000`. Only one known game, _Spacefight! 2091_, relies on this
    /// quirk, which was only present in the obscure CHIP-8 interpreter for the Amiga, while at
    /// least one game (_Animal Race_) relies on the standard behavior.
    /// * True: VF is set to 1 if the I register takes a value larger than `0x0FFF` (Amiga
    /// behavior)
    /// * False: VF is not affected by the I register (original behavior)
    #[serde(
        rename = "overflowIQuirks",
        deserialize_with = "some_bool_from_int",
        default
    )]
    pub overflow_i: Option<bool>,
}

/// Returns a default where no quirks are enabled, except the ones Octo observe.
impl Default for Quirks {
    fn default() -> Self {
        Self {
            shift: Some(false),
            load_store: Some(false),
            jump0: Some(false),
            logic: Some(false),
            clip: Some(false),
            vblank: Some(false),
            vf_order: Some(false),
            lores_dxy0: Some(LoResDxy0Behavior::default()),
            res_clear: Some(true),
            delay_wrap: Some(false),
            hires_collision: Some(false),
            clip_collision: Some(false),
            scroll: Some(false),
            overflow_i: Some(false),
        }
    }
}

/// Represents the different possible behaviors of attempting to draw a sprite with 0 height with
/// the instruction DXY0 while in lores (low-resolution 64x32) mode.
#[derive(Display, FromStr, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[display(style = "snake_case")]
pub enum LoResDxy0Behavior {
    /// No operation (original behavior)
    NoOp,
    /// Draw a sprite with height 16 (DREAM 6800 behavior)
    TallSprite,
    /// Draw a 16x16 sprite, ie. the same behavior as in hires (high-resolution 128x64 SUPER-CHIP
    /// XO-CHIP) mode (Octo behavior)
    BigSprite,
}

impl Default for LoResDxy0Behavior {
    fn default() -> Self {
        Self::BigSprite
    }
}

/// Representation of Octo options.
#[skip_serializing_none]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Options {
    /// The number of CHIP-8 instructions executed per 60Hz frame, ie. the "speed" of the virtual
    /// CPU. These are all approximations of hardware limitations, because on real hardware
    /// different instructions execute in different times, but it's a conventional middle ground.
    ///
    /// Common values:
    /// * 7–15 (approximate speed of the original interpreter for the COSMAC VIP)
    /// * 20–30 (approximate speed of the SUPER-CHIP interpreters for the HP 48 calculators)
    /// * 10000 (Octo's "Ludicrous speed" setting)
    #[serde(default, deserialize_with = "some_u16_from_int_or_str")]
    pub tickrate: Option<u16>,
    /// The maximum amount of virtual memory, in bytes, that is available to the program. If the CHIP-8 program is
    /// larger than this, the interpreter should give an error.
    ///
    /// At least 512 bytes are always reserved for the CHIP-8 interpreter and unavailable to the
    /// CHIP-8 game; see the field `start_address`.
    ///
    /// This is mostly relevant when developing CHIP-8 games for real hardware, as an assertion
    /// that the game will fit in the target platform's memory. Most CHIP-8 interpreters can ignore
    /// this value without consequence.
    ///
    /// Common values:
    /// * 3216 (original interpreter for the COSMAC VIP with 4K RAM)
    /// * 3583 (SUPER-CHIP interpreter for the HP 48)
    /// * 3584 (Octo)
    /// * 65024 (XO-CHIP interpreters)
    ///
    /// Other values might be used for games for more obscure platforms, games that were designed
    /// to run on a COSMAC VIP with only 2K RAM, etc.
    #[serde(default, deserialize_with = "some_u16_from_int_or_str")]
    pub max_size: Option<u16>, // {3216, 3583, 3584, 65024}
    /// The orientation of the display.
    #[serde(default)]
    pub screen_rotation: ScreenRotation,
    /// The font style expected by the game.
    #[serde(default)]
    pub font_style: Font,
    /// The touch controls this game supports.
    #[serde(default)]
    pub touch_input_mode: TouchMode, // OCTO_TOUCH_...
    /// The memory address in the virtual RAM that this game should be loaded from. On legacy
    /// hardware, the interpreter itself was loaded into the lower memory addresses, and then the
    /// game was loaded after it (usually at address `0x200`, ie. 512).
    ///
    /// Common values:
    /// * 512 (original interpreter for the COSMAC VIP, DREAM 6800, HP 48, etc)
    /// * 1536 (interpreter for the ETI-660)
    #[serde(default, deserialize_with = "some_u16_from_int_or_str")]
    pub start_address: Option<u16>,

    /// Custom colors this game would like to use, if possible. It's not important for a CHIP-8
    /// interpreter to support custom colors although not doing so might impact the creator's
    /// artistic vision, especially for XO-CHIP games that use more than two colors.
    #[serde(flatten)]
    pub colors: Colors,

    /// Specific behaviors this game expects from the interpreter in order to run properly. See
    /// [`OctoQuirks`] for specifics.
    #[serde(flatten)]
    pub quirks: Quirks,
}

/// Returns a default with a pretty fast tickrate, the maximum ROM size possible, and no quirks enabled except that the [`LoResDxy0Behavior`] assumes Octo behavior.
impl Default for Options {
    fn default() -> Self {
        Self {
            tickrate: Some(500),
            max_size: Some(65024),
            screen_rotation: ScreenRotation::default(),
            font_style: Font::default(),
            touch_input_mode: TouchMode::default(),
            start_address: Some(0x200),
            colors: Colors::default(),
            quirks: Quirks::default(),
        }
    }
}

/// Possible orientations of the display. Note that this should only affect the visual
/// representation of the screen; draw operations still act as if the screen rotation is 0. Only
/// used by some Octo games.
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug)]
#[repr(u16)]
pub enum ScreenRotation {
    /// Normal landscape screen display, used by 99.9999% of CHIP-8 games
    Normal = 0,
    /// Portrait screen display, ie. a normal screen rotated 90 degrees clockwise
    ClockWise = 90,
    /// Upside down landscape screen display
    UpsideDown = 180,
    /// Portrait screen display, ie. a normal screen rotated 90 degrees counter-clockwise
    CounterClockWise = 270,
}

impl Default for ScreenRotation {
    fn default() -> Self {
        Self::Normal
    }
}

/// Deserializes Options from a JSON string.
///
/// This format is used by Octo in Octocarts and HTML exports, as well as the Chip-8 Archive.
impl FromStr for Options {
    type Err = serde_json::Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        serde_json::from_str(s)
    }
}

impl Options {
    /// Deserializes Options from an INI string.
    ///
    /// # Errors
    ///
    /// Returns an `Err` if deserialization from the INI failed.
    pub fn from_ini(s: &str) -> Result<Self, serde_ini::de::Error> {
        Ok(Self::from(OptionsIni::from_str(s)?))
    }

    /// Serializes Options to an INI string.
    pub fn to_ini(self) -> String {
        OptionsIni::to_string(&OptionsIni::from(self))
    }

    /// Get a preset set of Options based on a target Platform.
    pub fn new(platform: Platform) -> Self {
        match platform {
            Platform::Octo => Self::default(),
            Platform::XoChip => Options {
                max_size: Some(65024),
                ..Self::default()
            },
            Platform::Vip => Self {
                tickrate: Some(20),
                max_size: Some(3216),
                screen_rotation: ScreenRotation::Normal,
                font_style: Font::Vip,
                touch_input_mode: TouchMode::None,
                start_address: Some(0x200),
                colors: Colors::default(),
                quirks: Quirks {
                    shift: Some(false),
                    load_store: Some(false),
                    jump0: Some(false),
                    logic: Some(true),
                    clip: Some(true),
                    vblank: Some(true),
                    vf_order: Some(true),
                    delay_wrap: Some(false),
                    overflow_i: Some(false),
                    lores_dxy0: Some(LoResDxy0Behavior::NoOp),
                    // The following are all None, as CHIP-8 on the VIP doesn't support high resolution:
                    hires_collision: None,
                    clip_collision: None,
                    scroll: None,
                    res_clear: None,
                },
            },
            Platform::Dream6800 => Self {
                tickrate: Some(20),
                max_size: Some(3216), // TODO check this
                screen_rotation: ScreenRotation::Normal,
                font_style: Font::Dream6800,
                touch_input_mode: TouchMode::None,
                start_address: Some(0x200),
                colors: Colors::default(),
                quirks: Quirks {
                    shift: Some(false),
                    load_store: Some(false),
                    jump0: Some(false),
                    logic: Some(true),
                    clip: Some(true),
                    vblank: Some(true),
                    vf_order: Some(true),
                    delay_wrap: Some(true),
                    overflow_i: Some(false),
                    lores_dxy0: Some(LoResDxy0Behavior::TallSprite),
                    // The following are all None, as CHIP-8 on the VIP doesn't support high resolution:
                    hires_collision: None,
                    clip_collision: None,
                    scroll: None,
                    res_clear: None,
                },
            },
            Platform::Eti660 => Self {
                tickrate: Some(20),
                max_size: Some(3216), // TODO check this
                screen_rotation: ScreenRotation::Normal,
                font_style: Font::Eti660,
                touch_input_mode: TouchMode::None,
                start_address: Some(0x600),
                colors: Colors::default(),
                quirks: Quirks {
                    // TODO check these
                    shift: Some(false),
                    load_store: Some(false),
                    jump0: Some(false),
                    logic: Some(true),
                    clip: Some(true),
                    vblank: Some(true),
                    vf_order: Some(true),
                    delay_wrap: Some(false),
                    overflow_i: Some(false),
                    lores_dxy0: Some(LoResDxy0Behavior::NoOp),
                    // The following are all None, as CHIP-8 on the VIP doesn't support high resolution:
                    hires_collision: None,
                    clip_collision: None,
                    scroll: None,
                    res_clear: None,
                },
            },
            Platform::Chip48 => Self {
                tickrate: Some(40),
                max_size: Some(3583), // TODO check this
                screen_rotation: ScreenRotation::Normal,
                font_style: Font::Schip, // TODO check this
                touch_input_mode: TouchMode::None,
                start_address: Some(0x200),
                colors: Colors::default(), // TODO LCD
                quirks: Quirks {
                    // TODO check these
                    shift: Some(true),
                    load_store: Some(true),
                    jump0: Some(true),
                    logic: Some(false),
                    clip: Some(true),
                    vblank: Some(false),
                    vf_order: None,
                    delay_wrap: Some(false),
                    overflow_i: Some(false),
                    lores_dxy0: Some(LoResDxy0Behavior::TallSprite), // TODO check this
                    res_clear: None,
                    hires_collision: None,
                    clip_collision: None,
                    scroll: None,
                },
            },
            Platform::Schip => Self {
                tickrate: Some(40),
                max_size: Some(3583),
                screen_rotation: ScreenRotation::Normal,
                font_style: Font::Schip,
                touch_input_mode: TouchMode::None,
                start_address: Some(0x200),
                colors: Colors::default(), // TODO LCD
                quirks: Quirks {
                    shift: Some(true),
                    load_store: Some(true),
                    jump0: Some(true),
                    logic: Some(false),
                    clip: Some(true),
                    vblank: Some(false),
                    vf_order: None, // TODO check this
                    res_clear: Some(false),
                    delay_wrap: Some(false),
                    overflow_i: Some(false),
                    lores_dxy0: Some(LoResDxy0Behavior::TallSprite),
                    hires_collision: Some(true),
                    clip_collision: Some(true),
                    scroll: Some(true),
                },
            },
        }
    }
}

/// Serializes Options into a JSON string.
///
/// This format is used by Octo in Octocarts and HTML exports, as well as the Chip-8 Archive.
impl fmt::Display for Options {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match serde_json::to_string(self) {
            Ok(string) => write!(f, "{}", string),
            _ => Err(fmt::Error),
        }
    }
}

// Could have used serde_aux::field_attributes::deserialize_option_number_from_string here
// but let's not pull in that dep just for this. If it had deserialize_option_bool_from_anything
// then we'd be talking.
fn some_u16_from_int_or_str<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Deserialize)]
    #[serde(untagged)]
    enum U16OrStr<'a> {
        U16(u16),
        Str(&'a str),
    }

    Ok(match U16OrStr::deserialize(deserializer)? {
        U16OrStr::Str(v) => match v.parse() {
            Ok(v) => Some(v),
            Err(_) => None,
        },
        U16OrStr::U16(v) => Some(v),
    })
}

fn some_bool_from_int<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Deserialize)]
    #[serde(untagged)]
    enum BoolOrU8 {
        Bool(bool),
        U8(u8),
    }

    match BoolOrU8::deserialize(deserializer)? {
        BoolOrU8::Bool(v) => Ok(Some(v)),
        BoolOrU8::U8(1) => Ok(Some(true)),
        BoolOrU8::U8(0) => Ok(Some(false)),
        BoolOrU8::U8(other) => Err(de::Error::invalid_value(
            Unexpected::Unsigned(u64::from(other)),
            &"zero or one",
        )),
    }
}

/// Represents the different fonts a CHIP-8 interpreter can provide.
///
/// It's not likely that many (or any) historical CHIP-8 games depend on a particular font, but it's
/// possible, and for that reason (and to make historical games look accurate) the font can be
/// overriden here _and_ you can get the sprite data for the fonts by calling [`get_font_data`].
#[derive(Display, FromStr, Debug, PartialEq, Serialize, Deserialize)]
// TODO: Should this actually be snakecase? https://github.com/JohnEarnest/c-octo#configuration-file
#[non_exhaustive]
pub enum Font {
    /// The font used by [Octo](https://github.com/JohnEarnest). Its small digits are identical to
    /// SUPER-CHIP's, but the big digits are a bigger version of the small ones, rather than
    /// SUPER-CHIP's rounded big digits. Contains small digits for 0–F, as well as big digits for 0–F.
    #[serde(rename = "octo")]
    #[display("octo")]
    Octo,
    /// The font used by the original CHIP-8 interpreter on the COSMAC VIP.
    /// Contains small digits for 0–F only.
    #[serde(rename = "vip")]
    #[display("vip")]
    Vip,
    /// The font used by CHIP-8/CHIPOS on the DREAM 6800. Contains small
    /// digits for 0–F only.
    #[serde(rename = "dream_6800")]
    #[display("dream_6800")]
    Dream6800,
    /// The font used by the CHIP-8 interpreter on the ETI-660. Contains small
    /// digits for 0–F only. Very similar to Dream6800.
    #[serde(rename = "eti_660")]
    #[display("eti_660")]
    Eti660,
    /// The font used by SUPER-CHIP 1.1 on the HP 48. Contains small digits for
    /// 0–F (identical to Octo's), but only big digits for 0–9.
    #[serde(rename = "schip")]
    #[display("schip")]
    Schip,
    /// Custom font used by the Fish'n'Chips CHIP-8 emulator. Contains small digits
    /// for 0–F and big digits (7x9 pixels) for 0–F.
    #[serde(rename = "fish")]
    #[display("fish")]
    Fish,
    /// Font designed by A-KouZ1, used by in the KChip-8 and FPChip-8 emulators. Contains small
    /// digits for 0–F and big digits for 0–F.
    #[serde(rename = "akouz1")]
    #[display("akouz1")]
    AKouZ1,
}

/// The default font is Octo's font, as it's the modern standard and contains all hexadecimal digits
/// in both small and large variants.
impl Default for Font {
    fn default() -> Self {
        Self::Octo
    }
}

impl Font {
    /// Returns a tuple where the first element is an array of 16 sprites that are 5 bytes tall, where
    /// each one represents the sprite data for a hexadecimal digit in a CHIP-8 font, and the other
    /// optional element is a vector of sprites that are 10 bytes tall.
    ///
    /// Note that some fonts are smaller than this, but the extra padding is still included so
    /// emulators and games can use the same routines regardless of the font.
    ///
    /// Not all fonts provide the larger sprites, as they became standard with SUPER-CHIP's high resolution mode.
    /// Furthermore, the SUPER-CHIP font set itself only provides large sprites for the decimal digits
    /// 0–9, not the hexadecimal A–F.
    ///
    /// A modern CHIP-8 interpreter will put its font data (for one font) somewhere in the first 512 bytes of
    /// memory, which are reserved for the interpreter, but the actual memory location doesn't matter.
    /// It's common to put it at either address 0 or 80 (`0x50`).
    pub fn get_font_data(&self) -> ([u8; 5 * 16], Option<Vec<u8>>) {
        match self {
            Font::Octo => (
                [
                    0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
                    0x20, 0x60, 0x20, 0x20, 0x70, // 1
                    0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
                    0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
                    0x90, 0x90, 0xF0, 0x10, 0x10, // 4
                    0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
                    0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
                    0xF0, 0x10, 0x20, 0x40, 0x40, // 7
                    0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
                    0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
                    0xF0, 0x90, 0xF0, 0x90, 0x90, // A
                    0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
                    0xF0, 0x80, 0x80, 0x80, 0xF0, // C
                    0xE0, 0x90, 0x90, 0x90, 0xE0, // D
                    0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
                    0xF0, 0x80, 0xF0, 0x80, 0x80, // F
                ],
                Some(vec![
                    0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, // 0
                    0x18, 0x78, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0xFF, // 1
                    0xFF, 0xFF, 0x03, 0x03, 0xFF, 0xFF, 0xC0, 0xC0, 0xFF, 0xFF, // 2
                    0xFF, 0xFF, 0x03, 0x03, 0xFF, 0xFF, 0x03, 0x03, 0xFF, 0xFF, // 3
                    0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0x03, 0x03, 0x03, 0x03, // 4
                    0xFF, 0xFF, 0xC0, 0xC0, 0xFF, 0xFF, 0x03, 0x03, 0xFF, 0xFF, // 5
                    0xFF, 0xFF, 0xC0, 0xC0, 0xFF, 0xFF, 0xC3, 0xC3, 0xFF, 0xFF, // 6
                    0xFF, 0xFF, 0x03, 0x03, 0x06, 0x0C, 0x18, 0x18, 0x18, 0x18, // 7
                    0xFF, 0xFF, 0xC3, 0xC3, 0xFF, 0xFF, 0xC3, 0xC3, 0xFF, 0xFF, // 8
                    0xFF, 0xFF, 0xC3, 0xC3, 0xFF, 0xFF, 0x03, 0x03, 0xFF, 0xFF, // 9
                    0x7E, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, // A
                    0xFC, 0xFC, 0xC3, 0xC3, 0xFC, 0xFC, 0xC3, 0xC3, 0xFC, 0xFC, // B
                    0x3C, 0xFF, 0xC3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC3, 0xFF, 0x3C, // C
                    0xFC, 0xFE, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xFE, 0xFC, // D
                    0xFF, 0xFF, 0xC0, 0xC0, 0xFF, 0xFF, 0xC0, 0xC0, 0xFF, 0xFF, // E
                    0xFF, 0xFF, 0xC0, 0xC0, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, // F
                ]),
            ),
            Font::Vip => (
                [
                    0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
                    0x60, 0x20, 0x20, 0x20, 0x70, // 1
                    0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
                    0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
                    0xA0, 0xA0, 0xF0, 0x20, 0x20, // 4
                    0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
                    0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
                    0xF0, 0x10, 0x10, 0x10, 0x10, // 7
                    0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
                    0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
                    0xF0, 0x90, 0xF0, 0x90, 0x90, // A
                    0xF0, 0x50, 0x70, 0x50, 0xF0, // B
                    0xF0, 0x80, 0x80, 0x80, 0xF0, // C
                    0xF0, 0x50, 0x50, 0x50, 0xF0, // D
                    0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
                    0xF0, 0x80, 0xF0, 0x80, 0x80, // F
                ],
                None,
            ),
            Font::Dream6800 => (
                [
                    0xE0, 0xA0, 0xA0, 0xA0, 0xE0, // 0
                    0x40, 0x40, 0x40, 0x40, 0x40, // 1
                    0xE0, 0x20, 0xE0, 0x80, 0xE0, // 2
                    0xE0, 0x20, 0xE0, 0x20, 0xE0, // 3
                    0x80, 0xA0, 0xA0, 0xE0, 0x20, // 4
                    0xE0, 0x80, 0xE0, 0x20, 0xE0, // 5
                    0xE0, 0x80, 0xE0, 0xA0, 0xE0, // 6
                    0xE0, 0x20, 0x20, 0x20, 0x20, // 7
                    0xE0, 0xA0, 0xE0, 0xA0, 0xE0, // 8
                    0xE0, 0xA0, 0xE0, 0x20, 0xE0, // 9
                    0xE0, 0xA0, 0xE0, 0xA0, 0xA0, // A
                    0xC0, 0xA0, 0xE0, 0xA0, 0xC0, // B
                    0xE0, 0x80, 0x80, 0x80, 0xE0, // C
                    0xC0, 0xA0, 0xA0, 0xA0, 0xC0, // D
                    0xE0, 0x80, 0xE0, 0x80, 0xE0, // E
                    0xE0, 0x80, 0xC0, 0x80, 0x80, // F
                ],
                None,
            ),
            Font::Eti660 => (
                [
                    0xE0, 0xA0, 0xA0, 0xA0, 0xE0, // 0
                    0x20, 0x20, 0x20, 0x20, 0x20, // 1
                    0xE0, 0x20, 0xE0, 0x80, 0xE0, // 2
                    0xE0, 0x20, 0xE0, 0x20, 0xE0, // 3
                    0xA0, 0xA0, 0xE0, 0x20, 0x20, // 4
                    0xE0, 0x80, 0xE0, 0x20, 0xE0, // 5
                    0xE0, 0x80, 0xE0, 0xA0, 0xE0, // 6
                    0xE0, 0x20, 0x20, 0x20, 0x20, // 7
                    0xE0, 0xA0, 0xE0, 0xA0, 0xE0, // 8
                    0xE0, 0xA0, 0xE0, 0x20, 0xE0, // 9
                    0xE0, 0xA0, 0xE0, 0xA0, 0xA0, // A
                    0x80, 0x80, 0xE0, 0xA0, 0xE0, // B
                    0xE0, 0x80, 0x80, 0x80, 0xE0, // C
                    0x20, 0x20, 0xE0, 0xA0, 0xE0, // D
                    0xE0, 0x80, 0xE0, 0x80, 0xE0, // E
                    0xE0, 0x80, 0xC0, 0x80, 0x80, // F
                ],
                None,
            ),
            Font::Schip => (
                [
                    0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
                    0x20, 0x60, 0x20, 0x20, 0x70, // 1
                    0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
                    0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
                    0x90, 0x90, 0xF0, 0x10, 0x10, // 4
                    0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
                    0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
                    0xF0, 0x10, 0x20, 0x40, 0x40, // 7
                    0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
                    0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
                    0xF0, 0x90, 0xF0, 0x90, 0x90, // A
                    0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
                    0xF0, 0x80, 0x80, 0x80, 0xF0, // C
                    0xE0, 0x90, 0x90, 0x90, 0xE0, // D
                    0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
                    0xF0, 0x80, 0xF0, 0x80, 0x80, // F
                ],
                Some(vec![
                    0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C, // 0
                    0x18, 0x38, 0x58, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, // 1
                    0x3E, 0x7F, 0xC3, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF, // 2
                    0x3C, 0x7E, 0xC3, 0x03, 0x0E, 0x0E, 0x03, 0xC3, 0x7E, 0x3C, // 3
                    0x06, 0x0E, 0x1E, 0x36, 0x66, 0xC6, 0xFF, 0xFF, 0x06, 0x06, // 4
                    0xFF, 0xFF, 0xC0, 0xC0, 0xFC, 0xFE, 0x03, 0xC3, 0x7E, 0x3C, // 5
                    0x3E, 0x7C, 0xE0, 0xC0, 0xFC, 0xFE, 0xC3, 0xC3, 0x7E, 0x3C, // 6
                    0xFF, 0xFF, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x60, 0x60, // 7
                    0x3C, 0x7E, 0xC3, 0xC3, 0x7E, 0x7E, 0xC3, 0xC3, 0x7E, 0x3C, // 8
                    0x3C, 0x7E, 0xC3, 0xC3, 0x7F, 0x3F, 0x03, 0x03, 0x3E, 0x7C, // 9
                ]),
            ),
            Font::Fish => (
                [
                    0x60, 0xA0, 0xA0, 0xA0, 0xC0, // 0
                    0x40, 0xC0, 0x40, 0x40, 0xE0, // 1
                    0xC0, 0x20, 0x40, 0x80, 0xE0, // 2
                    0xC0, 0x20, 0x40, 0x20, 0xC0, // 3
                    0x20, 0xA0, 0xE0, 0x20, 0x20, // 4
                    0xE0, 0x80, 0xC0, 0x20, 0xC0, // 5
                    0x40, 0x80, 0xC0, 0xA0, 0x40, // 6
                    0xE0, 0x20, 0x60, 0x40, 0x40, // 7
                    0x40, 0xA0, 0x40, 0xA0, 0x40, // 8
                    0x40, 0xA0, 0x60, 0x20, 0x40, // 9
                    0x40, 0xA0, 0xE0, 0xA0, 0xA0, // A
                    0xC0, 0xA0, 0xC0, 0xA0, 0xC0, // B
                    0x60, 0x80, 0x80, 0x80, 0x60, // C
                    0xC0, 0xA0, 0xA0, 0xA0, 0xC0, // D
                    0xE0, 0x80, 0xC0, 0x80, 0xE0, // E
                    0xE0, 0x80, 0xC0, 0x80, 0x80, // F
                ],
                Some(vec![
                    // Note: 7x9 pixels
                    0x7C, 0xC6, 0xCE, 0xDE, 0xD6, 0xF6, 0xE6, 0xC6, 0x7C, 0x00, // 0
                    0x10, 0x30, 0xF0, 0x30, 0x30, 0x30, 0x30, 0x30, 0xFC, 0x00, // 1
                    0x78, 0xCC, 0xCC, 0x0C, 0x18, 0x30, 0x60, 0xCC, 0xFC, 0x00, // 2
                    0x78, 0xCC, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0xCC, 0x78, 0x00, // 3
                    0x0C, 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x0C, 0x1E, 0x00, // 4
                    0xFC, 0xC0, 0xC0, 0xC0, 0xF8, 0x0C, 0x0C, 0xCC, 0x78, 0x00, // 5
                    0x38, 0x60, 0xC0, 0xC0, 0xF8, 0xCC, 0xCC, 0xCC, 0x78, 0x00, // 6
                    0xFE, 0xC6, 0xC6, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00, // 7
                    0x78, 0xCC, 0xCC, 0xEC, 0x78, 0xDC, 0xCC, 0xCC, 0x78, 0x00, // 8
                    0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0x18, 0x18, 0x30, 0x70, 0x00, // 9
                    0x30, 0x78, 0xCC, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0xCC, 0x00, // A
                    0xFC, 0x66, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x66, 0xFC, 0x00, // B
                    0x3C, 0x66, 0xC6, 0xC0, 0xC0, 0xC0, 0xC6, 0x66, 0x3C, 0x00, // C
                    0xF8, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0x00, // D
                    0xFE, 0x62, 0x60, 0x64, 0x7C, 0x64, 0x60, 0x62, 0xFE, 0x00, // E
                    0xFE, 0x66, 0x62, 0x64, 0x7C, 0x64, 0x60, 0x60, 0xF0, 0x00, // F
                ]),
            ),
            Font::AKouZ1 => (
                [
                    0x60, 0x90, 0x90, 0x90, 0x60, // 0
                    0x20, 0x60, 0x20, 0x20, 0x70, // 1
                    0xE0, 0x10, 0x60, 0x80, 0xF0, // 2
                    0xE0, 0x10, 0xE0, 0x10, 0xE0, // 3
                    0x30, 0x50, 0x90, 0xF0, 0x10, // 4
                    0xF0, 0x80, 0xF0, 0x10, 0xE0, // 5
                    0x70, 0x80, 0xF0, 0x90, 0x60, // 6
                    0xF0, 0x10, 0x20, 0x40, 0x40, // 7
                    0x60, 0x90, 0x60, 0x90, 0x60, // 8
                    0x60, 0x90, 0x70, 0x10, 0x60, // 9
                    0x60, 0x90, 0xF0, 0x90, 0x90, // A
                    0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
                    0x70, 0x80, 0x80, 0x80, 0x70, // C
                    0xE0, 0x90, 0x90, 0x90, 0xE0, // D
                    0xF0, 0x80, 0xE0, 0x80, 0xF0, // E
                    0xF0, 0x80, 0xE0, 0x80, 0x80, // F
                ],
                Some(vec![
                    0x7E, 0xC7, 0xC7, 0xCB, 0xCB, 0xD3, 0xD3, 0xE3, 0xE3, 0x7E, // 0
                    0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, // 1
                    0x7E, 0xC3, 0x03, 0x03, 0x0E, 0x18, 0x30, 0x60, 0xC0, 0xFF, // 2
                    0x7E, 0xC3, 0x03, 0x03, 0x1E, 0x03, 0x03, 0x03, 0xC3, 0x7E, // 3
                    0x06, 0x0E, 0x1E, 0x36, 0x66, 0xC6, 0xC6, 0xFF, 0x06, 0x06, // 4
                    0xFF, 0xC0, 0xC0, 0xC0, 0xFE, 0x03, 0x03, 0x03, 0xC3, 0x7E, // 5
                    0x7E, 0xC3, 0xC0, 0xC0, 0xFE, 0xC3, 0xC3, 0xC3, 0xC3, 0x7E, // 6
                    0xFF, 0x03, 0x03, 0x03, 0x06, 0x0C, 0x18, 0x18, 0x18, 0x18, // 7
                    0x7E, 0xC3, 0xC3, 0xC3, 0x7E, 0xC3, 0xC3, 0xC3, 0xC3, 0x7E, // 8
                    0x7E, 0xC3, 0xC3, 0xC3, 0x7F, 0x03, 0x03, 0x03, 0xC3, 0x7E, // 9
                    0x7E, 0xC3, 0xC3, 0xC3, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, // A
                    0xFE, 0xC3, 0xC3, 0xC3, 0xFE, 0xC3, 0xC3, 0xC3, 0xC3, 0xFE, // B
                    0x7E, 0xC3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC3, 0x7E, // C
                    0xFC, 0xC6, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC6, 0xFC, // D
                    0xFF, 0xC0, 0xC0, 0xC0, 0xFE, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF, // E
                    0xFF, 0xC0, 0xC0, 0xC0, 0xFE, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, // F
                ]),
            ),
        }
    }
}