├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── game-issue-template.md
└── workflows
│ └── main.yml
├── README.md
├── case
├── 3dprint-angryhelder
│ ├── GB Interceptor Bottom Base.step
│ ├── GB Interceptor Bottom Cap.step
│ ├── GB Interceptor Bottom.step
│ ├── GB Interceptor Button.step
│ └── GB Interceptor Top.step
├── 3dprint
│ ├── LICENSE
│ ├── case.blend
│ ├── case_bottom.stl
│ ├── case_button.stl
│ └── case_top.stl
└── README.md
├── firmware
├── CMakeLists.txt
├── LICENSE
├── README.md
├── cpubus.c
├── cpubus.h
├── debug.c
├── debug.h
├── font
│ └── font8x8_basic.h
├── gamedb
│ ├── build_games_h.py
│ ├── game_detection.c
│ ├── game_detection.h
│ ├── games.csv
│ └── games.h
├── jpeg
│ ├── base.jpg
│ ├── base_jpeg.h
│ ├── base_jpeg_no_chroma.h
│ ├── base_no_chroma.jpg
│ ├── generateBaseJpeg.py
│ ├── jpeg.c
│ ├── jpeg.h
│ ├── jpeg_encoding.pio
│ └── jpeg_prepare.pio
├── main.c
├── main.h
├── memory-bus.pio
├── opcodes.c
├── opcodes.h
├── osd.c
├── osd.h
├── pico_sdk_import.cmake
├── ppu.c
├── ppu.h
├── screens
│ ├── default.h
│ ├── default.png
│ ├── default.svg
│ ├── error.h
│ ├── error.png
│ ├── error.svg
│ ├── off.h
│ ├── off.png
│ ├── off.svg
│ └── png2header.sh
├── tusb_config.h
├── usb_descriptors.c
└── usb_descriptors.h
└── pcb
├── LICENSE
├── README.md
├── fabrication
├── bom
│ └── gb-stream-cart.csv
├── gerber
│ ├── gb-stream-cart-B_Cu.gbl
│ ├── gb-stream-cart-B_Mask.gbs
│ ├── gb-stream-cart-B_Paste.gbp
│ ├── gb-stream-cart-B_Silkscreen.gbo
│ ├── gb-stream-cart-Edge_Cuts.gm1
│ ├── gb-stream-cart-F_Cu.gtl
│ ├── gb-stream-cart-F_Mask.gts
│ ├── gb-stream-cart-F_Paste.gtp
│ ├── gb-stream-cart-F_Silkscreen.gto
│ ├── gb-stream-cart-NPTH-drl_map.gbr
│ ├── gb-stream-cart-NPTH.drl
│ ├── gb-stream-cart-PTH-drl_map.gbr
│ └── gb-stream-cart-PTH.drl
└── position
│ ├── gb-stream-cart-bottom-pos.csv
│ ├── gb-stream-cart-top-pos-fixed.csv
│ └── gb-stream-cart-top-pos.csv
└── kicad
├── MCU_RaspberryPi_RP2040.kicad_sym
├── RP2040_minimal.pretty
├── Crystal_SMD_HC49-US.kicad_mod
├── RP2040-QFN-56.kicad_mod
└── USB_Micro-B_Amphenol_10103594-0001LF_Horizontal_modified.kicad_mod
├── SN74LVC4245APWR.kicad_sym
├── gb-connector.kicad_sym
├── gb-stream-cart.kicad_pcb
├── gb-stream-cart.kicad_pro
├── gb-stream-cart.kicad_sch
└── gb-stream-cart.pretty
├── Fiducial_1mm_Mask1.7mm.kicad_mod
├── SN74LVC4245APWR.kicad_mod
├── cartridge-slot.kicad_mod
├── cartridge_PCBedge.kicad_mod
├── half-mouse-bite-3mm-slot.kicad_mod
├── mouse-bite-3mm-slot.kicad_mod
└── rail-mount.kicad_mod
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [Staacks]
4 | buy_me_a_coffee: there.oughta.be
5 |
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Host software (please complete the following information):**
27 | - OS: [e.g. Windows, MacOS, Linux, ...]
28 | - Software: [e.g. OBS, VLC, ffmpeg, Zoom, ...]
29 |
30 | **Game Boy:**
31 | - Model: [e.g. Game Boy Advance AGB-001]
32 |
33 | **Game:**
34 | - Title: [e.g. Tetris]
35 | - Version: [Usually printed on the cartridge, e.g. DMG-ZL-NOE]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/game-issue-template.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Game issue
3 | about: Bugs and problems with specific games
4 | title: ''
5 | labels: Game issue
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Type of issue**
11 | Interceptor reports error / Game looks different from Game Boy screen / other
12 |
13 | **Describe the bug**
14 | A clear and concise description of what the bug is.
15 |
16 | **To Reproduce**
17 | Steps to reproduce the behavior:
18 | 1. Go to '...'
19 | 2. Click on '....'
20 | 3. Scroll down to '....'
21 | 4. See error
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Host software (please complete the following information):**
27 | - OS: [e.g. Windows, MacOS, Linux, ...]
28 | - Software: [e.g. OBS, VLC, ffmpeg, Zoom, ...]
29 |
30 | **Game Boy:**
31 | - Model: [e.g. Game Boy Advance AGB-001]
32 |
33 | **Game:**
34 | - Title: [e.g. Tetris]
35 | - Version: [Usually printed on the cartridge, e.g. DMG-ZL-NOE]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Build gbinterceptor firmware
2 | on:
3 | push:
4 | pull_request:
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - name: Checkout
10 | uses: actions/checkout@main
11 | - name: Setup arm-none-eabi-gcc
12 | uses: carlosperate/arm-none-eabi-gcc-action@v1
13 | - name: Setup ninja
14 | uses: seanmiddleditch/gha-setup-ninja@master
15 | - name: Clone pico-sdk dependency (pinned versions of pico-sdk and tinyusb)
16 | run: |
17 | git clone https://github.com/raspberrypi/pico-sdk --recurse-submodules
18 | cd pico-sdk && sudo git checkout tags/2.0.0 && cd ..
19 | cd pico-sdk/lib/tinyusb && sudo git checkout 0.16.0 && cd ../../..
20 | - name: Build firmware
21 | run: |
22 | arm-none-eabi-gcc --version
23 | cd ./firmware
24 | mkdir build && cd build
25 | cmake .. -G Ninja -DPICO_SDK_PATH=../../pico-sdk
26 | ninja
27 | - name: 'Upload artifacts'
28 | uses: actions/upload-artifact@v4
29 | with:
30 | name: firmware
31 | path: |
32 | ./firmware/build/gb_interceptor.*
33 | retention-days: 30
34 | - name: Build base video mode firmware
35 | run: |
36 | cd ./firmware/build
37 | rm -rf -- ..?* .[!.]* *
38 | cmake .. -G Ninja -DBASE_VIDEO_MODE=1 -DPICO_SDK_PATH=../../pico-sdk
39 | ninja
40 | - name: 'Upload base video mode artifacts'
41 | uses: actions/upload-artifact@v4
42 | with:
43 | name: firmware_base_video_mode
44 | path: |
45 | ./firmware/build/gb_interceptor.*
46 | retention-days: 30
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GB Interceptor
2 | Capture or stream Game Boy gameplay footage via USB without modifying the Game Boy.
3 |
4 | This open source and open hardware Game Boy adapter uses an rp2040 to capture the communication on the cartridge bus. This data is fed into an emulator, rendered and then offered as a USB video class device.
5 |
6 | Details about the concept can be found at https://there.oughta.be/a/game-boy-capture-cartridge and an overview is given in the following video:
7 |
8 | [](https://youtu.be/6mOJtrFnawk)
9 |
10 | If you have pull-requests, bug reports or want to contribute new case designs, please do not hesitate to open an issue. For generic discussions, "show and tell" and if you are looking for support for something that is not a problem in the code here, I would recommend .
11 |
12 |
13 |
14 | # Warnings
15 |
16 | Please make sure to understand what this device does and what its limitations are. Most importantly, there is a little lag which will make it unsuitable for playing on a bis screen. It might be ok for slow games, but its focus is on recording and streaming.
17 |
18 | Also check out the compatibility tables for host software (for example OBS) and particular games. In principle this is a USB video class device and does not require drivers, but not all software supports the unsusual video format of the Interceptor. Similarly, games should usually work, but sometimes there are some details that the Interceptor does not yet support properly and some rare things that cannot work based on the principle of this device.
19 |
20 | * [Game Boy compatibility](https://github.com/Staacks/gbinterceptor/wiki/Game-Boy-compatibility)
21 | * [Host software compatibility](https://github.com/Staacks/gbinterceptor/wiki/Host-software-compatibility)
22 | * [Game compatibility](https://github.com/Staacks/gbinterceptor/wiki/Game-compatibility)
23 |
24 | # Building the Interceptor
25 |
26 | There is an [order and build video](https://youtu.be/Lg92tVkEE98) to guide you through the build process which I highly recommend, especially if you are not used to ordering PCBs. A [written guide can be found in this repository's wiki](https://github.com/Staacks/gbinterceptor/wiki/Build-guide).
27 |
28 | # Usage
29 |
30 | Simply plug the Interceptor into your Game Boy and the Game into the Interceptor. Connect the Interceptor to your computer before turning on the Game Boy. It should show up as a webcam in any software that supports USB Video devices (for example OBS Studio). Note that some software is not entirely happy with the exotic format, but it should work in most cases (see [Host software compatibility](https://github.com/Staacks/gbinterceptor/wiki/Host-software-compatibility)).
31 |
32 | Then just turn on the Game Boy and play.
33 |
34 | Note that you do not need to open the video stream first. The only important step is that the Interceptor is powered via USB before the Game Boy is turned on.
35 |
36 | ## Buttons
37 |
38 | The [Mode] button (next to the LEDs) allows to switch between grayscale/green colors and toggle frame blending. Each press switches colors and frame blending is toggled each time it returns to grayscale.
39 |
40 | The [Flash] button (near the center, usually inside the case) is only used to flash a new firmware version. Simply hold it while plugging in the USB cable. The Interceptor should appear as a storage device (like a USB stick) and you can simply drag the new firmware file (a uf2 file) into it.
41 |
42 | ## LEDs
43 |
44 | The red LED indicates that the Interceptor is powered.
45 |
46 | The blue LED stays on as long as the Interceptor is in sync with the game. (It may shortly turn off for a fraction of a second when the Game Boy turns the display off, i.e. when loading a level.)
47 |
48 | # Support
49 |
50 | While I do not give any guarantees I (and probably others too) am happy to help if I have the time.
51 |
52 | If you are having difficulties to order, build or use your Interceptor, please do not contact me directly but use [r/thereoughtabe on reddit](https://www.reddit.com/r/thereoughtabe/) as this allows others to help and the answer might help others, too.
53 |
54 | If you found a bug and in particular if you find glitches in a game, please check the [compatibility lists](https://github.com/Staacks/gbinterceptor/wiki) in the Wiki first to see if it is a known problem and open an issue here on Github otherwise.
55 |
56 | # Building the firmware
57 |
58 | Most users should just install the latest release uf2 as described in the [Build guide](https://github.com/Staacks/gbinterceptor/wiki/Build-guide), but if you want to build it yourself, you will need the [Raspberry Pi Pico SDK](https://github.com/raspberrypi/pico-sdk). Don't forget to set PICO_SDK_PATH accordingly.
59 |
60 | Make sure to check out the submodules too and specifically make sure that the [TinyUSB](https://github.com/hathach/tinyusb) submodule is at least on version 0.14.0 as the Pico SDK currently references an older version. (Also see [#3](https://github.com/Staacks/gbinterceptor/issues/3))
61 |
62 | # License
63 | The code is released under the GNU General Public Licence 3 and the design files (PCB layout and 3d printed case) are released under the Creative Commons licence CC-BY 4.0.
64 |
--------------------------------------------------------------------------------
/case/3dprint-angryhelder/GB Interceptor Button.step:
--------------------------------------------------------------------------------
1 | ISO-10303-21;
2 | HEADER;
3 | /* Generated by software containing ST-Developer
4 | * from STEP Tools, Inc. (www.steptools.com)
5 | */
6 |
7 | FILE_DESCRIPTION(
8 | /* description */ (''),
9 | /* implementation_level */ '2;1');
10 |
11 | FILE_NAME(
12 | /* name */
13 | 'C:/Users/Helder/Documents/eagle/projects/Gameboy Interceptor- USB Vid
14 | eo/GB Interceptor Button.step',
15 | /* time_stamp */ '2022-12-25T21:37:25-06:00',
16 | /* author */ (''),
17 | /* organization */ (''),
18 | /* preprocessor_version */ 'ST-DEVELOPER v18.1',
19 | /* originating_system */ 'Autodesk Translation Framework v10.13.0.1454',
20 |
21 | /* authorisation */ '');
22 |
23 | FILE_SCHEMA (('AUTOMOTIVE_DESIGN { 1 0 10303 214 3 1 1 }'));
24 | ENDSEC;
25 |
26 | DATA;
27 | #10=MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION('',(#13),#167);
28 | #11=SHAPE_REPRESENTATION_RELATIONSHIP('SRR','None',#174,#12);
29 | #12=ADVANCED_BREP_SHAPE_REPRESENTATION('',(#14),#166);
30 | #13=STYLED_ITEM('',(#183),#14);
31 | #14=MANIFOLD_SOLID_BREP('Button',#85);
32 | #15=FACE_BOUND('',#36,.T.);
33 | #16=PLANE('',#106);
34 | #17=PLANE('',#110);
35 | #18=PLANE('',#111);
36 | #19=LINE('',#154,#21);
37 | #20=LINE('',#160,#22);
38 | #21=VECTOR('',#128,1.95);
39 | #22=VECTOR('',#135,3.);
40 | #23=CYLINDRICAL_SURFACE('',#104,1.95);
41 | #24=CYLINDRICAL_SURFACE('',#107,3.);
42 | #25=FACE_OUTER_BOUND('',#31,.T.);
43 | #26=FACE_OUTER_BOUND('',#32,.T.);
44 | #27=FACE_OUTER_BOUND('',#33,.T.);
45 | #28=FACE_OUTER_BOUND('',#34,.T.);
46 | #29=FACE_OUTER_BOUND('',#35,.T.);
47 | #30=FACE_OUTER_BOUND('',#37,.T.);
48 | #31=EDGE_LOOP('',(#60,#61,#62,#63,#64));
49 | #32=EDGE_LOOP('',(#65,#66,#67,#68,#69));
50 | #33=EDGE_LOOP('',(#70));
51 | #34=EDGE_LOOP('',(#71,#72,#73,#74));
52 | #35=EDGE_LOOP('',(#75));
53 | #36=EDGE_LOOP('',(#76));
54 | #37=EDGE_LOOP('',(#77));
55 | #38=CIRCLE('',#100,1.45);
56 | #39=CIRCLE('',#101,0.5);
57 | #40=CIRCLE('',#102,1.95);
58 | #41=CIRCLE('',#103,1.95);
59 | #42=CIRCLE('',#105,1.95);
60 | #43=CIRCLE('',#108,3.);
61 | #44=CIRCLE('',#109,3.);
62 | #45=VERTEX_POINT('',#144);
63 | #46=VERTEX_POINT('',#146);
64 | #47=VERTEX_POINT('',#148);
65 | #48=VERTEX_POINT('',#152);
66 | #49=VERTEX_POINT('',#157);
67 | #50=VERTEX_POINT('',#159);
68 | #51=EDGE_CURVE('',#45,#45,#38,.T.);
69 | #52=EDGE_CURVE('',#45,#46,#39,.T.);
70 | #53=EDGE_CURVE('',#46,#47,#40,.T.);
71 | #54=EDGE_CURVE('',#47,#46,#41,.T.);
72 | #55=EDGE_CURVE('',#48,#48,#42,.T.);
73 | #56=EDGE_CURVE('',#48,#47,#19,.T.);
74 | #57=EDGE_CURVE('',#49,#49,#43,.T.);
75 | #58=EDGE_CURVE('',#49,#50,#20,.T.);
76 | #59=EDGE_CURVE('',#50,#50,#44,.T.);
77 | #60=ORIENTED_EDGE('',*,*,#51,.T.);
78 | #61=ORIENTED_EDGE('',*,*,#52,.T.);
79 | #62=ORIENTED_EDGE('',*,*,#53,.T.);
80 | #63=ORIENTED_EDGE('',*,*,#54,.T.);
81 | #64=ORIENTED_EDGE('',*,*,#52,.F.);
82 | #65=ORIENTED_EDGE('',*,*,#55,.F.);
83 | #66=ORIENTED_EDGE('',*,*,#56,.T.);
84 | #67=ORIENTED_EDGE('',*,*,#53,.F.);
85 | #68=ORIENTED_EDGE('',*,*,#54,.F.);
86 | #69=ORIENTED_EDGE('',*,*,#56,.F.);
87 | #70=ORIENTED_EDGE('',*,*,#51,.F.);
88 | #71=ORIENTED_EDGE('',*,*,#57,.F.);
89 | #72=ORIENTED_EDGE('',*,*,#58,.T.);
90 | #73=ORIENTED_EDGE('',*,*,#59,.F.);
91 | #74=ORIENTED_EDGE('',*,*,#58,.F.);
92 | #75=ORIENTED_EDGE('',*,*,#59,.T.);
93 | #76=ORIENTED_EDGE('',*,*,#55,.T.);
94 | #77=ORIENTED_EDGE('',*,*,#57,.T.);
95 | #78=TOROIDAL_SURFACE('',#99,1.45,0.5);
96 | #79=ADVANCED_FACE('',(#25),#78,.T.);
97 | #80=ADVANCED_FACE('',(#26),#23,.T.);
98 | #81=ADVANCED_FACE('',(#27),#16,.T.);
99 | #82=ADVANCED_FACE('',(#28),#24,.T.);
100 | #83=ADVANCED_FACE('',(#29,#15),#17,.T.);
101 | #84=ADVANCED_FACE('',(#30),#18,.F.);
102 | #85=CLOSED_SHELL('',(#79,#80,#81,#82,#83,#84));
103 | #86=DERIVED_UNIT_ELEMENT(#88,1.);
104 | #87=DERIVED_UNIT_ELEMENT(#169,-3.);
105 | #88=(
106 | MASS_UNIT()
107 | NAMED_UNIT(*)
108 | SI_UNIT(.KILO.,.GRAM.)
109 | );
110 | #89=DERIVED_UNIT((#86,#87));
111 | #90=MEASURE_REPRESENTATION_ITEM('density measure',
112 | POSITIVE_RATIO_MEASURE(7850.),#89);
113 | #91=PROPERTY_DEFINITION_REPRESENTATION(#96,#93);
114 | #92=PROPERTY_DEFINITION_REPRESENTATION(#97,#94);
115 | #93=REPRESENTATION('material name',(#95),#166);
116 | #94=REPRESENTATION('density',(#90),#166);
117 | #95=DESCRIPTIVE_REPRESENTATION_ITEM('Steel','Steel');
118 | #96=PROPERTY_DEFINITION('material property','material name',#176);
119 | #97=PROPERTY_DEFINITION('material property','density of part',#176);
120 | #98=AXIS2_PLACEMENT_3D('placement',#142,#112,#113);
121 | #99=AXIS2_PLACEMENT_3D('',#143,#114,#115);
122 | #100=AXIS2_PLACEMENT_3D('',#145,#116,#117);
123 | #101=AXIS2_PLACEMENT_3D('',#147,#118,#119);
124 | #102=AXIS2_PLACEMENT_3D('',#149,#120,#121);
125 | #103=AXIS2_PLACEMENT_3D('',#150,#122,#123);
126 | #104=AXIS2_PLACEMENT_3D('',#151,#124,#125);
127 | #105=AXIS2_PLACEMENT_3D('',#153,#126,#127);
128 | #106=AXIS2_PLACEMENT_3D('',#155,#129,#130);
129 | #107=AXIS2_PLACEMENT_3D('',#156,#131,#132);
130 | #108=AXIS2_PLACEMENT_3D('',#158,#133,#134);
131 | #109=AXIS2_PLACEMENT_3D('',#161,#136,#137);
132 | #110=AXIS2_PLACEMENT_3D('',#162,#138,#139);
133 | #111=AXIS2_PLACEMENT_3D('',#163,#140,#141);
134 | #112=DIRECTION('axis',(0.,0.,1.));
135 | #113=DIRECTION('refdir',(1.,0.,0.));
136 | #114=DIRECTION('center_axis',(0.,0.,1.));
137 | #115=DIRECTION('ref_axis',(1.,0.,0.));
138 | #116=DIRECTION('center_axis',(0.,0.,-1.));
139 | #117=DIRECTION('ref_axis',(-1.,0.,0.));
140 | #118=DIRECTION('center_axis',(0.,-1.,0.));
141 | #119=DIRECTION('ref_axis',(-1.,0.,0.));
142 | #120=DIRECTION('center_axis',(0.,0.,1.));
143 | #121=DIRECTION('ref_axis',(-1.,0.,0.));
144 | #122=DIRECTION('center_axis',(0.,0.,1.));
145 | #123=DIRECTION('ref_axis',(-1.,0.,0.));
146 | #124=DIRECTION('center_axis',(0.,0.,-1.));
147 | #125=DIRECTION('ref_axis',(-1.,0.,0.));
148 | #126=DIRECTION('center_axis',(0.,0.,-1.));
149 | #127=DIRECTION('ref_axis',(-1.,0.,0.));
150 | #128=DIRECTION('',(0.,0.,1.));
151 | #129=DIRECTION('center_axis',(0.,0.,1.));
152 | #130=DIRECTION('ref_axis',(-1.,0.,0.));
153 | #131=DIRECTION('center_axis',(0.,0.,-1.));
154 | #132=DIRECTION('ref_axis',(-1.,0.,0.));
155 | #133=DIRECTION('center_axis',(0.,0.,-1.));
156 | #134=DIRECTION('ref_axis',(-1.,0.,0.));
157 | #135=DIRECTION('',(0.,0.,1.));
158 | #136=DIRECTION('center_axis',(0.,0.,1.));
159 | #137=DIRECTION('ref_axis',(-1.,0.,0.));
160 | #138=DIRECTION('center_axis',(0.,0.,1.));
161 | #139=DIRECTION('ref_axis',(-1.,0.,0.));
162 | #140=DIRECTION('center_axis',(0.,0.,1.));
163 | #141=DIRECTION('ref_axis',(-1.,0.,0.));
164 | #142=CARTESIAN_POINT('',(0.,0.,0.));
165 | #143=CARTESIAN_POINT('Origin',(-30.3052050582665,55.6170748852214,-4.11763990114109));
166 | #144=CARTESIAN_POINT('',(-31.7552050582665,55.6170748852214,-3.61763990114109));
167 | #145=CARTESIAN_POINT('Origin',(-30.3052050582665,55.6170748852214,-3.61763990114109));
168 | #146=CARTESIAN_POINT('',(-32.2552050582664,55.6170748852214,-4.11763990114109));
169 | #147=CARTESIAN_POINT('Origin',(-31.7552050582665,55.6170748852214,-4.11763990114109));
170 | #148=CARTESIAN_POINT('',(-28.3552050582665,55.6170748852214,-4.11763990114109));
171 | #149=CARTESIAN_POINT('Origin',(-30.3052050582665,55.6170748852214,-4.11763990114109));
172 | #150=CARTESIAN_POINT('Origin',(-30.3052050582665,55.6170748852214,-4.11763990114109));
173 | #151=CARTESIAN_POINT('Origin',(-30.3052050582665,55.6170748852214,-3.61763990114109));
174 | #152=CARTESIAN_POINT('',(-28.3552050582665,55.6170748852214,-6.61764002035038));
175 | #153=CARTESIAN_POINT('Origin',(-30.3052050582665,55.6170748852214,-6.61764002035038));
176 | #154=CARTESIAN_POINT('',(-28.3552050582665,55.6170748852214,-3.61763990114109));
177 | #155=CARTESIAN_POINT('Origin',(-30.3052050582665,55.6170748852214,-3.61763990114109));
178 | #156=CARTESIAN_POINT('Origin',(-30.3079463432022,55.617195175656,-6.61764002035038));
179 | #157=CARTESIAN_POINT('',(-27.3079463432022,55.617195175656,-8.61764002035038));
180 | #158=CARTESIAN_POINT('Origin',(-30.3079463432022,55.617195175656,-8.61764002035038));
181 | #159=CARTESIAN_POINT('',(-27.3079463432022,55.617195175656,-6.61764002035038));
182 | #160=CARTESIAN_POINT('',(-27.3079463432022,55.617195175656,-6.61764002035038));
183 | #161=CARTESIAN_POINT('Origin',(-30.3079463432022,55.617195175656,-6.61764002035038));
184 | #162=CARTESIAN_POINT('Origin',(-30.3079463432022,55.617195175656,-6.61764002035038));
185 | #163=CARTESIAN_POINT('Origin',(-30.3079463432022,55.617195175656,-8.61764002035038));
186 | #164=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(0.01),#168,
187 | 'DISTANCE_ACCURACY_VALUE',
188 | 'Maximum model space distance between geometric entities at asserted c
189 | onnectivities');
190 | #165=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(0.01),#168,
191 | 'DISTANCE_ACCURACY_VALUE',
192 | 'Maximum model space distance between geometric entities at asserted c
193 | onnectivities');
194 | #166=(
195 | GEOMETRIC_REPRESENTATION_CONTEXT(3)
196 | GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#164))
197 | GLOBAL_UNIT_ASSIGNED_CONTEXT((#168,#170,#171))
198 | REPRESENTATION_CONTEXT('','3D')
199 | );
200 | #167=(
201 | GEOMETRIC_REPRESENTATION_CONTEXT(3)
202 | GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#165))
203 | GLOBAL_UNIT_ASSIGNED_CONTEXT((#168,#170,#171))
204 | REPRESENTATION_CONTEXT('','3D')
205 | );
206 | #168=(
207 | LENGTH_UNIT()
208 | NAMED_UNIT(*)
209 | SI_UNIT(.MILLI.,.METRE.)
210 | );
211 | #169=(
212 | LENGTH_UNIT()
213 | NAMED_UNIT(*)
214 | SI_UNIT($,.METRE.)
215 | );
216 | #170=(
217 | NAMED_UNIT(*)
218 | PLANE_ANGLE_UNIT()
219 | SI_UNIT($,.RADIAN.)
220 | );
221 | #171=(
222 | NAMED_UNIT(*)
223 | SI_UNIT($,.STERADIAN.)
224 | SOLID_ANGLE_UNIT()
225 | );
226 | #172=SHAPE_DEFINITION_REPRESENTATION(#173,#174);
227 | #173=PRODUCT_DEFINITION_SHAPE('',$,#176);
228 | #174=SHAPE_REPRESENTATION('',(#98),#166);
229 | #175=PRODUCT_DEFINITION_CONTEXT('part definition',#180,'design');
230 | #176=PRODUCT_DEFINITION('GB Interceptor Shell',
231 | 'GB Interceptor Shell v11',#177,#175);
232 | #177=PRODUCT_DEFINITION_FORMATION('',$,#182);
233 | #178=PRODUCT_RELATED_PRODUCT_CATEGORY('GB Interceptor Shell v11',
234 | 'GB Interceptor Shell v11',(#182));
235 | #179=APPLICATION_PROTOCOL_DEFINITION('international standard',
236 | 'automotive_design',2009,#180);
237 | #180=APPLICATION_CONTEXT(
238 | 'Core Data for Automotive Mechanical Design Process');
239 | #181=PRODUCT_CONTEXT('part definition',#180,'mechanical');
240 | #182=PRODUCT('GB Interceptor Shell','GB Interceptor Shell v11',$,(#181));
241 | #183=PRESENTATION_STYLE_ASSIGNMENT((#184));
242 | #184=SURFACE_STYLE_USAGE(.BOTH.,#185);
243 | #185=SURFACE_SIDE_STYLE('',(#186));
244 | #186=SURFACE_STYLE_FILL_AREA(#187);
245 | #187=FILL_AREA_STYLE('Steel - Satin',(#188));
246 | #188=FILL_AREA_STYLE_COLOUR('Steel - Satin',#189);
247 | #189=COLOUR_RGB('Steel - Satin',0.627450980392157,0.627450980392157,0.627450980392157);
248 | ENDSEC;
249 | END-ISO-10303-21;
250 |
--------------------------------------------------------------------------------
/case/3dprint/case.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Staacks/gbinterceptor/14213d507fb056389c3be0bd35f8b57b359a52ea/case/3dprint/case.blend
--------------------------------------------------------------------------------
/case/3dprint/case_bottom.stl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Staacks/gbinterceptor/14213d507fb056389c3be0bd35f8b57b359a52ea/case/3dprint/case_bottom.stl
--------------------------------------------------------------------------------
/case/3dprint/case_button.stl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Staacks/gbinterceptor/14213d507fb056389c3be0bd35f8b57b359a52ea/case/3dprint/case_button.stl
--------------------------------------------------------------------------------
/case/3dprint/case_top.stl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Staacks/gbinterceptor/14213d507fb056389c3be0bd35f8b57b359a52ea/case/3dprint/case_top.stl
--------------------------------------------------------------------------------
/case/README.md:
--------------------------------------------------------------------------------
1 | # GB Interceptor Case
2 |
3 | These are various resources and design files for cases for the GB Interceptor. The 3d-printed one referenced in the Youtube videos and the build guide [build guide](https://github.com/Staacks/gbinterceptor/wiki/Build-guide) can be found in the folder 3dprint.
4 |
5 | **You can find more options by the community by looking at the [case guides](https://github.com/Staacks/gbinterceptor/wiki#case-options) on the Wiki.**
6 |
7 | # Important note about the break-out extension board
8 |
9 | If you want to create a custom case using the extension board to place the cartridge slot in a more flexible manner, please take special care to correctly match the pins. For this, ignore the silklayer (printing) ob the PCB and look at the position of pin one, which is the one with the square pad (the others are circular). The printed outline on the PCB may work fine for jumper wires or special connectors, but with the normal ribbon cable connectors (known from old disk drives) the pin headers of either the extension board or the GB Interceptor need to be soldered onto the opposite side of the board in order for the pins to match. Therefore, keep track of pin one when creating your own solution.
10 |
11 | # Folder list
12 |
13 | Please, find details about each option in the [case guides](https://github.com/Staacks/gbinterceptor/wiki#case-options) on the Wiki.
14 |
15 | * __3dprint__
16 | The original 3d printed case as discussed in the videos.
17 | * __3dprint-angryhelder__
18 | Variant by AngryHelder (Helder Silva) of the original 3d printed case. These are step files derived from the original 3dprint case to allow for easy adaption into new case projects. It also includes a version that is easier and nicer to print but requires glueing.
19 |
20 |
21 | # License
22 |
23 | The design files are licensed under the Creative Commons Attribution License (CC-BY 4.0).
24 |
--------------------------------------------------------------------------------
/firmware/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.13)
2 |
3 | include(pico_sdk_import.cmake)
4 |
5 | project(gb_interceptor C CXX ASM)
6 | set(CMAKE_C_STANDARD 11)
7 | set(CMAKE_CXX_STANDARD 17)
8 |
9 | pico_sdk_init()
10 |
11 | add_executable(gb_interceptor)
12 |
13 | target_sources(gb_interceptor PUBLIC
14 | ${CMAKE_CURRENT_LIST_DIR}/main.c
15 | ${CMAKE_CURRENT_LIST_DIR}/cpubus.c
16 | ${CMAKE_CURRENT_LIST_DIR}/opcodes.c
17 | ${CMAKE_CURRENT_LIST_DIR}/ppu.c
18 | ${CMAKE_CURRENT_LIST_DIR}/jpeg/jpeg.c
19 | ${CMAKE_CURRENT_LIST_DIR}/osd.c
20 | ${CMAKE_CURRENT_LIST_DIR}/debug.c
21 | ${CMAKE_CURRENT_LIST_DIR}/usb_descriptors.c
22 | ${CMAKE_CURRENT_LIST_DIR}/gamedb/game_detection.c
23 | )
24 |
25 | target_link_libraries(gb_interceptor PUBLIC pico_stdlib hardware_pio pico_multicore hardware_interp hardware_dma tinyusb_device tinyusb_board pico_unique_id)
26 |
27 | target_include_directories(gb_interceptor PUBLIC ${CMAKE_CURRENT_LIST_DIR})
28 |
29 | target_compile_definitions(gb_interceptor PUBLIC PICO_XOSC_STARTUP_DELAY_MULTIPLIER=64)
30 |
31 | pico_add_extra_outputs(gb_interceptor)
32 |
33 | pico_enable_stdio_usb(gb_interceptor 1)
34 | pico_enable_stdio_uart(gb_interceptor 0)
35 |
36 | pico_set_binary_type(gb_interceptor copy_to_ram)
37 |
38 | pico_generate_pio_header(gb_interceptor
39 | ${CMAKE_CURRENT_LIST_DIR}/memory-bus.pio
40 | )
41 |
42 | pico_generate_pio_header(gb_interceptor
43 | ${CMAKE_CURRENT_LIST_DIR}/jpeg/jpeg_prepare.pio
44 | )
45 |
46 | pico_generate_pio_header(gb_interceptor
47 | ${CMAKE_CURRENT_LIST_DIR}/jpeg/jpeg_encoding.pio
48 | )
49 |
50 | if (DEFINED BASE_VIDEO_MODE)
51 | MESSAGE(STATUS "Building BASE_VIDEO_MODE variant")
52 | add_compile_definitions("BASE_VIDEO_MODE") #Uncomment for base video mode version with fixed 30fps and no frame blending
53 | endif()
54 |
--------------------------------------------------------------------------------
/firmware/README.md:
--------------------------------------------------------------------------------
1 | # GB Interceptor Firmware
2 |
3 | This folder contains the source code for the firmware of the GB Interceptor. It requires the Raspberry Pi Pico SDK as well as TinyUSB (which is usually a submodule of the Pico SDK repository). If you want to build the firmware yourself, remember to set `PICO_SDK_PATH` correctly.
4 |
5 | The subfolder screens contains the info screens shown when no game is running. The `sh` script in the same folder can be used to convert new images to header files.
6 |
7 | # License
8 |
9 | This code is licensed under GNU General Public License v3.
10 |
--------------------------------------------------------------------------------
/firmware/cpubus.h:
--------------------------------------------------------------------------------
1 | #ifndef GBINTERCEPTOR_CPUBUS
2 | #define GBINTERCEPTOR_CPUBUS
3 |
4 | #include "pico/stdlib.h"
5 | #include "hardware/pio.h"
6 | #include "ppu.h"
7 |
8 | extern uint32_t cycleRatio;
9 |
10 | extern volatile bool running;
11 | extern volatile const char * error;
12 | extern volatile int errorOpcode;
13 |
14 | extern PIO busPIO;
15 | extern uint32_t busPIOemptyMask, busPIOstallMask;
16 | extern uint busSM;
17 |
18 | extern uint32_t volatile rawBusData;
19 | extern uint8_t volatile * opcode;
20 | extern uint16_t volatile * address;
21 | extern uint8_t volatile * extra;
22 |
23 | #define HISTORY_READAHEAD 5
24 | extern uint32_t history[];
25 | extern uint volatile cycleIndex;
26 | extern uint8_t volatile * historyIndex; //Index for history array, lowest byte of cycleIndex
27 | extern uint volatile div;
28 |
29 | extern uint ignoreCycles;
30 | extern bool cartridgeDMA;
31 | extern uint cartridgeDMAsrc;
32 | extern uint cartridgeDMAdst;
33 |
34 | extern mutex_t cpubusMutex;
35 | #define DUMPMORE 10 //Additional lines to dump after error
36 |
37 |
38 | extern volatile uint8_t memory[];
39 |
40 | //CPU registers
41 | extern uint8_t registers[];
42 | extern uint8_t * b;
43 | extern uint8_t * c;
44 | extern uint8_t * d;
45 | extern uint8_t * e;
46 | extern uint8_t * h;
47 | extern uint8_t * l;
48 | // +6 is unused to align af with opcode order
49 | extern uint8_t * a;
50 | extern uint8_t * f;
51 | extern uint16_t * bc;
52 | extern uint16_t * de;
53 | extern uint16_t * hl;
54 | extern uint16_t sp;
55 |
56 | extern uint32_t flags;
57 | extern uint8_t *Z, *N, *H, *C;
58 |
59 | extern bool interruptsEnabled;
60 | extern uint interruptsEnableCycle;
61 |
62 | void handleMemoryBus();
63 |
64 | void getNextFromBus();
65 |
66 | void dmaToOAM(uint16_t source);
67 |
68 | void stop(const char* errorMsg);
69 |
70 | #endif
--------------------------------------------------------------------------------
/firmware/debug.c:
--------------------------------------------------------------------------------
1 | #include "debug.h"
2 |
3 | #include
4 | #include "pico/stdlib.h"
5 |
6 | #include "cpubus.h"
7 |
8 | #ifdef DEBUG_LOG_REGISTERS
9 | uint32_t volatile registerHistory32[64][2];
10 | uint16_t volatile spHistory[64];
11 | uint32_t volatile flagHistory[64];
12 | #endif
13 |
14 | #ifdef DEBUG_PPU_TIMING
15 | bool recordPPUTiming = false;
16 | bool ppuTimingReady = false;
17 | bool recordPPUTimingStarted = false;
18 | uint ppuTiming[LINES][4];//OAM search start, OAM search done, rendering start, rendering done
19 | struct PPUTimingEvents ppuTimingEvents;
20 | #endif
21 |
22 | #ifdef DEBUG_EVENTS
23 | const char * opcodeNames[256] = {
24 | "NOP ","LD BC,d16 ","LD (BC),A ","INC BC ","INC B ","DEC B ","LD B,d8 ","RLCA ","LD (a16),SP","ADD HL,BC ","LD A,(BC) ","DEC BC ","INC C ","DEC C ","LD C,d8 ","RRCA ",
25 | "STOP 0 ","LD DE,d16 ","LD (DE),A ","INC DE ","INC D ","DEC D ","LD D,d8 ","RLA ","JR r8 ","ADD HL,DE ","LD A,(DE) ","DEC DE ","INC E ","DEC E ","LD E,d8 ","RRA ",
26 | "JR NZ,r8 ","LD HL,d16 ","LD (HL+),A ","INC HL ","INC H ","DEC H ","LD H,d8 ","DAA ","JR Z,r8 ","ADD HL,HL ","LD A,(HL+) ","DEC HL ","INC L ","DEC L ","LD L,d8 ","CPL ",
27 | "JR NC,r8 ","LD SP,d16 ","LD (HL-),A ","INC SP ","INC (HL) ","DEC (HL) ","LD (HL),d8 ","SCF ","JR C,r8 ","ADD HL,SP ","LD A,(HL-) ","DEC SP ","INC A ","DEC A ","LD A,d8 ","CCF ",
28 | "LD B,B ","LD B,C ","LD B,D ","LD B,E ","LD B,H ","LD B,L ","LD B,(HL) ","LD B,A ","LD C,B ","LD C,C ","LD C,D ","LD C,E ","LD C,H ","LD C,L ","LD C,(HL) ","LD C,A ",
29 | "LD D,B ","LD D,C ","LD D,D ","LD D,E ","LD D,H ","LD D,L ","LD D,(HL) ","LD D,A ","LD E,B ","LD E,C ","LD E,D ","LD E,E ","LD E,H ","LD E,L ","LD E,(HL) ","LD E,A ",
30 | "LD H,B ","LD H,C ","LD H,D ","LD H,E ","LD H,H ","LD H,L ","LD H,(HL) ","LD H,A ","LD L,B ","LD L,C ","LD L,D ","LD L,E ","LD L,H ","LD L,L ","LD L,(HL) ","LD L,A ",
31 | "LD (HL),B ","LD (HL),C ","LD (HL),D ","LD (HL),E ","LD (HL),H ","LD (HL),L ","HALT ","LD (HL),A ","LD A,B ","LD A,C ","LD A,D ","LD A,E ","LD A,H ","LD A,L ","LD A,(HL) ","LD A,A ",
32 | "ADD A,B ","ADD A,C ","ADD A,D ","ADD A,E ","ADD A,H ","ADD A,L ","ADD A,(HL) ","ADD A,A ","ADC A,B ","ADC A,C ","ADC A,D ","ADC A,E ","ADC A,H ","ADC A,L ","ADC A,(HL) ","ADC A,A ",
33 | "SUB B ","SUB C ","SUB D ","SUB E ","SUB H ","SUB L ","SUB (HL) ","SUB A ","SBC A,B ","SBC A,C ","SBC A,D ","SBC A,E ","SBC A,H ","SBC A,L ","SBC A,(HL) ","SBC A,A ",
34 | "AND B ","AND C ","AND D ","AND E ","AND H ","AND L ","AND (HL) ","AND A ","XOR B ","XOR C ","XOR D ","XOR E ","XOR H ","XOR L ","XOR (HL) ","XOR A ",
35 | "OR B ","OR C ","OR D ","OR E ","OR H ","OR L ","OR (HL) ","OR A ","CP B ","CP C ","CP D ","CP E ","CP H ","CP L ","CP (HL) ","CP A ",
36 | "RET NZ ","POP BC ","JP NZ,a16 ","JP a16 ","CALL NZ,a16","PUSH BC ","ADD A,d8 ","RST 00H ","RET Z ","RET ","JP Z,a16 ","PREFIX CB ","CALL Z,a16 ","CALL a16 ","ADC A,d8 ","RST 08H ",
37 | "RET NC ","POP DE ","JP NC,a16 ","ERROR ","CALL NC,a16","PUSH DE ","SUB d8 ","RST 10H ","RET C ","RETI ","JP C,a16 ","ERROR ","CALL C,a16 ","ERROR ","SBC A,d8 ","RST 18H ",
38 | "LDH (a8),A ","POP HL ","LD (C),A ","ERROR ","ERROR ","PUSH HL ","AND d8 ","RST 20H ","ADD SP,r8 ","JP (HL) ","LD (a16),A ","ERROR ","ERROR ","ERROR ","XOR d8 ","RST 28H ",
39 | "LDH A,(a8) ","POP AF ","LD A,(C) ","DI ","ERROR ","PUSH AF ","OR d8 ","RST 30H ","LD HL,SP+r8","LD SP,HL ","LD A,(a16) ","EI ","ERROR ","ERROR ","CP d8 ","RST 38H "
40 | };
41 |
42 | const char * opcodeNames16bit[256] = {
43 | "RLC B ","RLC C ","RLC D ","RLC E ","RLC H ","RLC L ","RLC (HL) ","RLC A ","RRC B ","RRC C ","RRC D ","RRC E ","RRC H ","RRC L ","RRC (HL) ","RRC A ",
44 | "RL B ","RL C ","RL D ","RL E ","RL H ","RL L ","RL (HL) ","RL A ","RR B ","RR C ","RR D ","RR E ","RR H ","RR L ","RR (HL) ","RR A ",
45 | "SLA B ","SLA C ","SLA D ","SLA E ","SLA H ","SLA L ","SLA (HL) ","SLA A ","SRA B ","SRA C ","SRA D ","SRA E ","SRA H ","SRA L ","SRA (HL) ","SRA A ",
46 | "SWAP B ","SWAP C ","SWAP D ","SWAP E ","SWAP H ","SWAP L ","SWAP (HL) ","SWAP A ","SRL B ","SRL C ","SRL D ","SRL E ","SRL H ","SRL L ","SRL (HL) ","SRL A ",
47 | "BIT 0,B ","BIT 0,C ","BIT 0,D ","BIT 0,E ","BIT 0,H ","BIT 0,L ","BIT 0,(HL) ","BIT 0,A ","BIT 1,B ","BIT 1,C ","BIT 1,D ","BIT 1,E ","BIT 1,H ","BIT 1,L ","BIT 1,(HL) ","BIT 1,A ",
48 | "BIT 2,B ","BIT 2,C ","BIT 2,D ","BIT 2,E ","BIT 2,H ","BIT 2,L ","BIT 2,(HL) ","BIT 2,A ","BIT 3,B ","BIT 3,C ","BIT 3,D ","BIT 3,E ","BIT 3,H ","BIT 3,L ","BIT 3,(HL) ","BIT 3,A ",
49 | "BIT 4,B ","BIT 4,C ","BIT 4,D ","BIT 4,E ","BIT 4,H ","BIT 4,L ","BIT 4,(HL) ","BIT 4,A ","BIT 5,B ","BIT 5,C ","BIT 5,D ","BIT 5,E ","BIT 5,H ","BIT 5,L ","BIT 5,(HL) ","BIT 5,A ",
50 | "BIT 6,B ","BIT 6,C ","BIT 6,D ","BIT 6,E ","BIT 6,H ","BIT 6,L ","BIT 6,(HL) ","BIT 6,A ","BIT 7,B ","BIT 7,C ","BIT 7,D ","BIT 7,E ","BIT 7,H ","BIT 7,L ","BIT 7,(HL) ","BIT 7,A ",
51 | "RES 0,B ","RES 0,C ","RES 0,D ","RES 0,E ","RES 0,H ","RES 0,L ","RES 0,(HL) ","RES 0,A ","RES 1,B ","RES 1,C ","RES 1,D ","RES 1,E ","RES 1,H ","RES 1,L ","RES 1,(HL) ","RES 1,A ",
52 | "RES 2,B ","RES 2,C ","RES 2,D ","RES 2,E ","RES 2,H ","RES 2,L ","RES 2,(HL) ","RES 2,A ","RES 3,B ","RES 3,C ","RES 3,D ","RES 3,E ","RES 3,H ","RES 3,L ","RES 3,(HL) ","RES 3,A ",
53 | "RES 4,B ","RES 4,C ","RES 4,D ","RES 4,E ","RES 4,H ","RES 4,L ","RES 4,(HL) ","RES 4,A ","RES 5,B ","RES 5,C ","RES 5,D ","RES 5,E ","RES 5,H ","RES 5,L ","RES 5,(HL) ","RES 5,A ",
54 | "RES 6,B ","RES 6,C ","RES 6,D ","RES 6,E ","RES 6,H ","RES 6,L ","RES 6,(HL) ","RES 6,A ","RES 7,B ","RES 7,C ","RES 7,D ","RES 7,E ","RES 7,H ","RES 7,L ","RES 7,(HL) ","RES 7,A ",
55 | "SET 0,B ","SET 0,C ","SET 0,D ","SET 0,E ","SET 0,H ","SET 0,L ","SET 0,(HL) ","SET 0,A ","SET 1,B ","SET 1,C ","SET 1,D ","SET 1,E ","SET 1,H ","SET 1,L ","SET 1,(HL) ","SET 1,A ",
56 | "SET 2,B ","SET 2,C ","SET 2,D ","SET 2,E ","SET 2,H ","SET 2,L ","SET 2,(HL) ","SET 2,A ","SET 3,B ","SET 3,C ","SET 3,D ","SET 3,E ","SET 3,H ","SET 3,L ","SET 3,(HL) ","SET 3,A ",
57 | "SET 4,B ","SET 4,C ","SET 4,D ","SET 4,E ","SET 4,H ","SET 4,L ","SET 4,(HL) ","SET 4,A ","SET 5,B ","SET 5,C ","SET 5,D ","SET 5,E ","SET 5,H ","SET 5,L ","SET 5,(HL) ","SET 5,A ",
58 | "SET 6,B ","SET 6,C ","SET 6,D ","SET 6,E ","SET 6,H ","SET 6,L ","SET 6,(HL) ","SET 6,A ","SET 7,B ","SET 7,C ","SET 7,D ","SET 7,E ","SET 7,H ","SET 7,L ","SET 7,(HL) ","SET 7,A "
59 | };
60 | #endif
61 |
62 | void dumpBus() { //Dump opcode history
63 | bool pastIndicator = false;
64 | uint8_t dumpIndicator = *historyIndex - DUMPMORE + HISTORY_READAHEAD;
65 | *historyIndex += HISTORY_READAHEAD;
66 | printf("\n===============================\n");
67 | bool isPrefixOpcode = false;
68 | for (int i = 0; i < 256; i++) {
69 | (*historyIndex)++;
70 | const uint32_t b = history[*historyIndex];
71 | const char * event = " ";
72 | #ifdef DEBUG_EVENTS
73 | if (!pastIndicator) {
74 | if ((b & 0x01000000) != 0) {
75 | isPrefixOpcode = ((uint8_t)(b >> 16) == 0xcb);
76 | event = opcodeNames[(uint8_t)(b >> 16)];
77 | } else if ((b & 0x02000000) != 0) {
78 | event = "IRQ ";
79 | } else if (isPrefixOpcode) {
80 | event = opcodeNames16bit[(uint8_t)(b >> 16)];
81 | isPrefixOpcode = false;
82 | }
83 | }
84 | #endif
85 | printf("%s %02x %s %s %s %04x %02x %s",
86 | *historyIndex == dumpIndicator ? ">" : " ",
87 | (uint8_t)(b >> 24),
88 | b & 0x20000000 ? "nWR" : " WR",
89 | b & 0x40000000 ? "nRD" : " RD",
90 | b & 0x80000000 ? "nCS" : " CS",
91 | (uint16_t)b,
92 | (uint8_t)(b >> 16),
93 | event);
94 | #ifdef DEBUG_LOG_REGISTERS
95 | if ((b & 0x01000000) != 0 && i >= 0xc0) {
96 | uint8_t volatile * reg = (uint8_t volatile *)®isterHistory32[*historyIndex & 0x3f];
97 | uint8_t volatile * flags = (uint8_t volatile*)&flagHistory[*historyIndex & 0x3f];
98 | printf(" A=%02x BC=%02x%02x DE=%02x%02x HL=%02x%02x SP=%04x %s%s%s%s",
99 | reg[6], reg[1], reg[0], reg[3], reg[2], reg[5], reg[4], //See registers in cpubus.c about the uninstuitive order
100 | spHistory[*historyIndex & 0x3f],
101 | flags[0] ? "Z" : "-", flags[1] ? "N" : "-", flags[2] ? "H" : "-", flags[3] ? "C" : "-"
102 | );
103 | }
104 | #endif
105 | if (*historyIndex == dumpIndicator)
106 | pastIndicator = true;
107 | printf("\n");
108 | }
109 | printf("\n===============================\n\n");
110 |
111 | printf("Registers:\n");
112 | printf("A B C D E H L SP Flags\n");
113 | printf("%02x %02x %02x %02x %02x %02x %02x %04x %s %s %s %s\n", *a, *b, *c, *d , *e, *h, *l, sp, *Z ? "Z" : "-", *N ? "N" : "-", *H ? "H" : "-", *C ? "C" : "-");
114 |
115 | printf("\n===============================\n\n");
116 |
117 | if (error != NULL)
118 | printf("%s\n", error);
119 | if (errorOpcode >= 0)
120 | printf("Opcode: %02x\n", errorOpcode);
121 | }
122 |
123 | #ifdef DEBUG_PPU_TIMING
124 |
125 | void printPPUTiming() {
126 | ppuTimingReady = false;
127 | printf("\n ===============================\n");
128 | printf("| y | OAM SEARCH | RENDERING |\n");
129 | printf("|-----|------------|------------|\n");
130 | for (int line = 0; line < LINES; line++) {
131 | printf("|% 4d |% 4d ..% 4d |% 4d ..% 4d |\n", line, ppuTiming[line][0], ppuTiming[line][1], ppuTiming[line][2], ppuTiming[line][3]);
132 | }
133 | printf(" ===============================\n");
134 | printf("Frame: %d .. %d => %d (Should be %d), vblank adjusted by %d\n\n", ppuTimingEvents.frameStartCycle, ppuTimingEvents.frameEndCycle, ppuTimingEvents.frameEndCycle - ppuTimingEvents.frameStartCycle, CYCLES_PER_FRAME, ppuTimingEvents.vblankOffset);
135 | }
136 |
137 | #endif
138 |
139 | void dumpMemory() {
140 | //Dump our copy of the memory
141 | bool skipping = false;
142 | for (int baseAddr = 0x8000; baseAddr < 0x010000; baseAddr += 0x10) {
143 | bool skipThis = true;
144 | for (int subAddr = 0x00; subAddr < 0x10; subAddr++)
145 | if (memory[baseAddr | subAddr] != 0)
146 | skipThis = false;
147 | if (skipThis) {
148 | if (!skipping) {
149 | skipping = true;
150 | printf("....\n");
151 | }
152 | } else {
153 | skipping = false;
154 | printf("%04x ", baseAddr);
155 | for (int subAddr = 0x00; subAddr < 0x10; subAddr++) {
156 | printf("%02x ", memory[baseAddr | subAddr]);
157 | if (subAddr == 0x07)
158 | printf(" ");
159 | }
160 | printf("\n");
161 | }
162 | }
163 | printf("\n");
164 | }
--------------------------------------------------------------------------------
/firmware/debug.h:
--------------------------------------------------------------------------------
1 | #ifndef GBINTERCEPTOR_DEBUG
2 | #define GBINTERCEPTOR_DEBUG
3 |
4 | #include "pico/stdlib.h"
5 | #include "ppu.h"
6 |
7 | #define DEBUG_EVENTS //Mark events like opcodes or interrupts in the bus history. This is mostly harmless, but disabling it might save a few cycles in a problematic part of a game.
8 | //#define DEBUG_GAME_DETECTION //Periodically outputs the current hashes used for game detection
9 | //#define DEBUG_PPU_TIMING //Periodically measures the timing of PPU rendering and outputs it via USB serial
10 | //#define DEBUG_MEMORY_DUMP //Disables rendering and periodically dumps the emulated memory to USB serial
11 | //#define DEBUG_BREAKPOINT_AT_ADDRESS 0x3926 //Trigger a breakpoint if an opcode at the given address is about to be executed and dump memory and opcode history
12 | //#define DEBUG_BREAKPOINT_AT_ADDRESS_IGNORE 73 //The break at DEBUG_BREAKPOINT_AT_ADDRESS will be ignored n times.
13 | //#define DEBUG_BREAKPOINT_AT_WRITE_TO_ADDRESS 0xc854 //Trigger a breakpoint if data is written to a specific address
14 | //#define DEBUG_BREAKPOINT_AT_WRITE_TO_ADDRESS_IGNORE 3 //The break at DEBUG_BREAKPOINT_AT_WRITE_TO_ADDRESS will be ignored n times.
15 | //#define DEBUG_BREAKPOINT_AT_READ_FROM_ADDRESS 0xa007 //Trigger a breakpoint if data is read from a specific address
16 | //#define DEBUG_BREAKPOINT_AT_READ_FROM_ADDRESS_IGNORE 0 //The break at DEBUG_BREAKPOINT_AT_READ_FROM_ADDRESS will be ignored n times.
17 | //#define DEBUG_LOG_REGISTERS //Log register values in the history, this takes a few cycles from the critical rp2040 core and might cause PIO stall problems. Note, that for some reason I don't understand this messes badly with vsync.
18 |
19 | #ifdef DEBUG_BREAKPOINT_AT_ADDRESS
20 | #define DEBUG_TRIGGER_BREAKPOINT_AT_ADDRESS \
21 | static uint debug_breakpoint_at_address_counter = DEBUG_BREAKPOINT_AT_ADDRESS_IGNORE; \
22 | if (*address == DEBUG_BREAKPOINT_AT_ADDRESS) { \
23 | if (debug_breakpoint_at_address_counter) { \
24 | debug_breakpoint_at_address_counter--; \
25 | } else { \
26 | running = false; \
27 | error = "Address breakpoint."; \
28 | } \
29 | }
30 | #else
31 | #define DEBUG_TRIGGER_BREAKPOINT_AT_ADDRESS
32 | #endif
33 |
34 | #ifdef DEBUG_BREAKPOINT_AT_WRITE_TO_ADDRESS
35 | #define DEBUG_TRIGGER_BREAKPOINT_AT_WRITE_TO_ADDRESS \
36 | static uint debug_breakpoint_at_write_to_address_counter = DEBUG_BREAKPOINT_AT_WRITE_TO_ADDRESS_IGNORE; \
37 | if (address == DEBUG_BREAKPOINT_AT_WRITE_TO_ADDRESS) { \
38 | if (debug_breakpoint_at_write_to_address_counter) { \
39 | debug_breakpoint_at_write_to_address_counter--; \
40 | } else { \
41 | running = false; \
42 | error = "Write breakpoint."; \
43 | } \
44 | }
45 | #else
46 | #define DEBUG_TRIGGER_BREAKPOINT_AT_WRITE_TO_ADDRESS
47 | #endif
48 |
49 | #ifdef DEBUG_BREAKPOINT_AT_READ_FROM_ADDRESS
50 | #define DEBUG_TRIGGER_BREAKPOINT_AT_READ_FROM_ADDRESS \
51 | static uint debug_breakpoint_at_read_from_address_counter = DEBUG_BREAKPOINT_AT_READ_FROM_ADDRESS_IGNORE; \
52 | if (*address == DEBUG_BREAKPOINT_AT_READ_FROM_ADDRESS) { \
53 | if (debug_breakpoint_at_read_from_address_counter) { \
54 | debug_breakpoint_at_read_from_address_counter--; \
55 | } else { \
56 | running = false; \
57 | error = "Read breakpoint."; \
58 | } \
59 | }
60 | #else
61 | #define DEBUG_TRIGGER_BREAKPOINT_AT_READ_FROM_ADDRESS
62 | #endif
63 |
64 |
65 | #ifdef DEBUG_PPU_TIMING
66 | extern bool recordPPUTiming, recordPPUTimingStarted, ppuTimingReady;
67 |
68 | extern uint ppuTiming[LINES][4];
69 |
70 | struct PPUTimingEvents {
71 | uint frameStartCycle;
72 | uint frameEndCycle;
73 | int vblankOffset;
74 | };
75 |
76 | extern struct PPUTimingEvents ppuTimingEvents;
77 |
78 | void printPPUTiming();
79 |
80 | #define DEBUG_MARK_YRESET \
81 | if (recordPPUTiming) { \
82 | if (recordPPUTimingStarted) { \
83 | ppuTimingEvents.frameEndCycle = cycleIndex; \
84 | recordPPUTiming = false; \
85 | recordPPUTimingStarted = false; \
86 | ppuTimingReady = true; \
87 | } else { \
88 | recordPPUTimingStarted = true; \
89 | ppuTimingEvents.vblankOffset = 0; \
90 | ppuTimingEvents.frameStartCycle = cycleIndex; \
91 | for (int i = 0; i < LINES; i++) { \
92 | ppuTiming[i][0] = 0; \
93 | ppuTiming[i][1] = 0; \
94 | ppuTiming[i][2] = 0; \
95 | ppuTiming[i][3] = 0; \
96 | } \
97 | } \
98 | }
99 |
100 | #define DEBUG_MARK_OAMSEARCHSTART \
101 | if (recordPPUTiming) { \
102 | ppuTiming[y][0] = lineCycle; \
103 | }
104 |
105 | #define DEBUG_MARK_OAMSEARCHSTOP \
106 | if (recordPPUTiming) { \
107 | if (ppuTiming[y][1] == 0) \
108 | ppuTiming[y][1] = lineCycle; \
109 | }
110 |
111 | #define DEBUG_MARK_RENDERSTART \
112 | if (recordPPUTiming) { \
113 | ppuTiming[y][2] = lineCycle; \
114 | }
115 |
116 | #define DEBUG_MARK_RENDERSTOP \
117 | if (recordPPUTiming) { \
118 | ppuTiming[y][3] = lineCycle; \
119 | }
120 |
121 | #define DEBUG_MARK_VBLANK_ADJUST \
122 | if (recordPPUTiming && ppuTimingEvents.vblankOffset == 0) { \
123 | ppuTimingEvents.vblankOffset = vblankOffset; \
124 | }
125 |
126 | #else
127 |
128 | #define DEBUG_MARK_YRESET
129 | #define DEBUG_MARK_OAMSEARCHSTART
130 | #define DEBUG_MARK_OAMSEARCHSTOP
131 | #define DEBUG_MARK_RENDERSTART
132 | #define DEBUG_MARK_RENDERSTOP
133 | #define DEBUG_MARK_VBLANK_ADJUST
134 |
135 | #endif
136 |
137 | #ifdef DEBUG_LOG_REGISTERS
138 | extern uint32_t volatile registerHistory32[64][2]; //We only log the last 64 events to save memory
139 | extern uint16_t volatile spHistory[64];
140 | extern uint32_t volatile flagHistory[64];
141 | #define DEBUG_TRIGGER_LOG_REGISTERS \
142 | registerHistory32[*historyIndex & 0x3f][0] = *((uint32_t *)(®isters[0])); \
143 | registerHistory32[*historyIndex & 0x3f][1] = *((uint32_t *)(®isters[4])); \
144 | spHistory[*historyIndex & 0x3f] = sp; \
145 | flagHistory[*historyIndex & 0x3f] = flags;
146 |
147 | #else
148 |
149 | #define DEBUG_TRIGGER_LOG_REGISTERS
150 |
151 | #endif
152 |
153 | void dumpMemory();
154 | void dumpBus();
155 |
156 | #endif
--------------------------------------------------------------------------------
/firmware/font/font8x8_basic.h:
--------------------------------------------------------------------------------
1 | /**
2 | * 8x8 monochrome bitmap fonts for rendering
3 | * Author: Daniel Hepper
4 | *
5 | * License: Public Domain
6 | *
7 | * Based on:
8 | * // Summary: font8x8.h
9 | * // 8x8 monochrome bitmap fonts for rendering
10 | * //
11 | * // Author:
12 | * // Marcel Sondaar
13 | * // International Business Machines (public domain VGA fonts)
14 | * //
15 | * // License:
16 | * // Public Domain
17 | *
18 | * Fetched from: http://dimensionalrift.homelinux.net/combuster/mos3/?p=viewsource&file=/modules/gfx/font8_8.asm
19 | **/
20 |
21 | // Constant: font8x8_basic
22 | // Contains an 8x8 font map for unicode points U+0000 - U+007F (basic latin)
23 | char font8x8_basic[128][8] = {
24 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul)
25 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001
26 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002
27 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003
28 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004
29 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005
30 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006
31 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007
32 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008
33 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009
34 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A
35 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B
36 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C
37 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D
38 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E
39 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F
40 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010
41 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011
42 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012
43 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013
44 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014
45 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015
46 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016
47 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017
48 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018
49 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019
50 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A
51 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B
52 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C
53 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D
54 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E
55 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F
56 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space)
57 | { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!)
58 | { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (")
59 | { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#)
60 | { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($)
61 | { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%)
62 | { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&)
63 | { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (')
64 | { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (()
65 | { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ())
66 | { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*)
67 | { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+)
68 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,)
69 | { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-)
70 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.)
71 | { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/)
72 | { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0)
73 | { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1)
74 | { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2)
75 | { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3)
76 | { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4)
77 | { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5)
78 | { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6)
79 | { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7)
80 | { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8)
81 | { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9)
82 | { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:)
83 | { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (;)
84 | { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<)
85 | { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=)
86 | { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>)
87 | { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?)
88 | { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@)
89 | { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A)
90 | { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B)
91 | { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C)
92 | { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D)
93 | { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E)
94 | { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F)
95 | { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G)
96 | { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H)
97 | { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I)
98 | { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J)
99 | { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K)
100 | { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L)
101 | { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M)
102 | { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N)
103 | { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O)
104 | { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P)
105 | { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q)
106 | { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R)
107 | { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S)
108 | { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T)
109 | { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U)
110 | { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V)
111 | { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W)
112 | { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X)
113 | { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y)
114 | { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z)
115 | { 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([)
116 | { 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\)
117 | { 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (])
118 | { 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^)
119 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_)
120 | { 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`)
121 | { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a)
122 | { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b)
123 | { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c)
124 | { 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d)
125 | { 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e)
126 | { 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f)
127 | { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g)
128 | { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h)
129 | { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i)
130 | { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j)
131 | { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k)
132 | { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l)
133 | { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m)
134 | { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n)
135 | { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o)
136 | { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p)
137 | { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q)
138 | { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r)
139 | { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s)
140 | { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t)
141 | { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u)
142 | { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v)
143 | { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w)
144 | { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x)
145 | { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y)
146 | { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z)
147 | { 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({)
148 | { 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|)
149 | { 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (})
150 | { 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~)
151 | { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // U+007F
152 | };
153 |
--------------------------------------------------------------------------------
/firmware/gamedb/build_games_h.py:
--------------------------------------------------------------------------------
1 | ###
2 | # This Python script generates the content of games.h.
3 | # Do not edit games.h directly, but change games.csv and run this script to
4 | # update games.h (on Linux systems just redirect the output to games.h)
5 | # The reason for not editing games.h directly is that the data has to be
6 | # sorted by vramHash2, the precompiler define for the list length has to be
7 | # set correctly and the entries of gameInfosDirectory have to point at the
8 | # right indices.
9 | ###
10 |
11 | import csv
12 | import re
13 | import sys
14 |
15 | gameInfos = []
16 | gameInfosDirectory = []
17 | maxBranchBasedFixCount = 0
18 | maxRegisterDuringDMACount = 0
19 |
20 | with open('games.csv') as csvfile:
21 | reader = csv.DictReader(csvfile, delimiter=',', quotechar='"', skipinitialspace=True)
22 | currentDirectoryIndex = -1
23 | for row in sorted(reader, key=lambda d: d["vramHash2"]):
24 | hashIndex = (int(row["vramHash2"], 16) >> 24) & 0xff
25 | while hashIndex > currentDirectoryIndex:
26 | gameInfosDirectory.append(len(gameInfos))
27 | currentDirectoryIndex += 1
28 | gameInfo = {}
29 | gameInfo["vramHash1"] = row["vramHash1"]
30 | gameInfo["vramHash2"] = row["vramHash2"]
31 | gameInfo["title"] = row["title"]
32 | gameInfo["branchBasedFixes"] = []
33 | if row["fixes"] != "":
34 | for fix in row["fixes"].split("|"):
35 | parts = re.search("\s*([a-zA-Z]+)\((.+)\)\s*", fix)
36 | if parts == None:
37 | print("Parse error: " + fix, file=sys.stderr)
38 | if parts[1].lower() == "dmaFix".lower():
39 | gameInfo["dmaFix"] = parts[2]
40 | elif parts[1].lower() == "useImmediateIRQ".lower():
41 | gameInfo["useImmediateIRQ"] = parts[2]
42 | elif parts[1].lower() == "disableStatSyncs".lower():
43 | gameInfo["disableStatSyncs"] = parts[2]
44 | elif parts[1].lower() == "disableLySyncs".lower():
45 | gameInfo["disableLySyncs"] = parts[2]
46 | elif parts[1].lower() == "windowLineAlwaysPauses".lower():
47 | gameInfo["windowLineAlwaysPauses"] = parts[2]
48 | elif parts[1].lower() == "branchBasedFix".lower():
49 | branchBasedFixParameters = parts[2].split(",")
50 | branchBasedFix = {}
51 | branchBasedFix["jumpAddress"] = branchBasedFixParameters[0].strip()
52 | branchBasedFix["fixTarget"] = branchBasedFixParameters[1].strip()
53 | branchBasedFix["takenMethod"] = branchBasedFixParameters[2].strip()
54 | branchBasedFix["takenValue"] = branchBasedFixParameters[3].strip()
55 | branchBasedFix["notTakenMethod"] = branchBasedFixParameters[4].strip()
56 | branchBasedFix["notTakenValue"] = branchBasedFixParameters[5].strip()
57 | gameInfo["branchBasedFixes"].append(branchBasedFix)
58 | if len(gameInfo["branchBasedFixes"]) > maxBranchBasedFixCount:
59 | maxBranchBasedFixCount = len(gameInfo["branchBasedFixes"])
60 | elif parts[1].lower() == "writeRegistersDuringDMA".lower():
61 | registerDuringDMAParameters = parts[2].split(",")
62 | if len(registerDuringDMAParameters) > maxRegisterDuringDMACount:
63 | maxRegisterDuringDMACount = len(registerDuringDMAParameters)
64 | gameInfo["writeRegistersDuringDMA"] = parts[2]
65 | else:
66 | print("Unknown fix: " + fix, file=sys.stderr)
67 | gameInfo["comment"] = row["comment"]
68 | gameInfos.append(gameInfo)
69 |
70 | while 256 > currentDirectoryIndex:
71 | gameInfosDirectory.append(len(gameInfos))
72 | currentDirectoryIndex += 1
73 |
74 | print("// Do not edit this file directly!")
75 | print("// Edit games.csv instead and use the Python script build_games_h.py to regenerate games.c.");
76 | print("")
77 | print("#ifndef GBINTERCEPTOR_GAMES")
78 | print("#define GBINTERCEPTOR_GAMES")
79 | print("#define GAME_LIST_SIZE " + str(len(gameInfos)))
80 | print("")
81 | print("GameInfo __in_flash(\"games\") gameInfos[GAME_LIST_SIZE] = {")
82 | for gameInfo in gameInfos:
83 | print(" {", end="")
84 | print(".vramHash1 = " + gameInfo["vramHash1"] + ", ", end="")
85 | print(".vramHash2 = " + gameInfo["vramHash2"] + ", ", end="")
86 | print(".dmaFix = " + gameInfo.get("dmaFix", "0x0000") + ", ", end="")
87 | print(".useImmediateIRQ = " + gameInfo.get("useImmediateIRQ", "false") + ", ", end="")
88 | print(".disableStatSyncs = " + gameInfo.get("disableStatSyncs", "false") + ", ", end="")
89 | print(".disableLySyncs = " + gameInfo.get("disableLySyncs", "false") + ", ", end="")
90 | print(".windowLineAlwaysPauses = " + gameInfo.get("windowLineAlwaysPauses", "false") + ", ", end="")
91 | print(".branchBasedFixes = {", end="")
92 | for branchBasedFix in gameInfo["branchBasedFixes"]:
93 | print("{", end="")
94 | print(".jumpAddress = " + branchBasedFix["jumpAddress"] + ", ", end="")
95 | print(".fixTarget = " + branchBasedFix["fixTarget"] + ", ", end="")
96 | print(".takenMethod = " + branchBasedFix["takenMethod"] + ", ", end="")
97 | print(".takenValue = " + branchBasedFix["takenValue"] + ", ", end="")
98 | print(".notTakenMethod = " + branchBasedFix["notTakenMethod"] + ", ", end="")
99 | print(".notTakenValue = " + branchBasedFix["notTakenValue"], end="")
100 | print("}, ", end="")
101 | print("}, ", end="")
102 | print(".writeRegistersDuringDMA = {" + gameInfo.get("writeRegistersDuringDMA", "") + "}, ", end="")
103 | print(".title = \"" + gameInfo["title"] + "\", " + " "*(18-len(gameInfo["title"])), end="")
104 | print("}, // " + gameInfo["comment"])
105 |
106 | print("};")
107 | print("")
108 | print("uint16_t gameInfoDirectory[257] = {")
109 | print(" " + ", ".join(map(str, gameInfosDirectory)))
110 | print("};")
111 | print("#endif")
112 | print("All done. Make sure that BRANCH_BASED_FIX_LIST_SIZE is at least " + str(maxBranchBasedFixCount) + " and DMA_REGISTER_MAP_SIZE is at least " + str(maxRegisterDuringDMACount) + " in game_detection.h.", file=sys.stderr)
113 |
114 |
--------------------------------------------------------------------------------
/firmware/gamedb/game_detection.c:
--------------------------------------------------------------------------------
1 | #include "game_detection.h"
2 | #include "games.h"
3 |
4 | #include
5 |
6 | #include "debug.h"
7 |
8 | volatile uint vramHash1, vramHash2;
9 |
10 | GameInfo gameInfo;
11 | volatile bool gameDetected = false;
12 |
13 | void resetHashes() {
14 | gameDetected = false;
15 | gameInfo.dmaFix = 0x0000;
16 | gameInfo.useImmediateIRQ = false;
17 | gameInfo.disableStatSyncs = false;
18 | gameInfo.disableLySyncs = false;
19 | gameInfo.windowLineAlwaysPauses = false;
20 | gameInfo.branchBasedFixes[0].jumpAddress = 0x0000;
21 | gameInfo.writeRegistersDuringDMA[0] = 0x00;
22 |
23 | vramHash1 = 0;
24 | vramHash2 = 0;
25 | }
26 |
27 | bool detectGame() {
28 | #if defined(DEBUG_GAME_DETECTION)
29 | printf("Hashes: VRAM Hash1=0x%08x Hash2=0x%08x\n", vramHash1, vramHash2);
30 | #endif
31 | uint start = gameInfoDirectory[vramHash2 >> 24];
32 | uint end = gameInfoDirectory[(vramHash2 >> 24) + 1];
33 | while (start < end) {
34 | uint mid = start + (end - start) / 2;
35 | if (gameInfos[mid].vramHash2 == vramHash2) {
36 | if (gameInfos[mid].vramHash1 == vramHash1) {
37 | gameDetected = true;
38 | gameInfo = gameInfos[mid];
39 | printf("Detected %s\n", gameInfo.title);
40 | return true;
41 | } else if (gameInfos[mid].vramHash1 < vramHash1) {
42 | start = mid + 1;
43 | } else {
44 | end = mid;
45 | }
46 | } else if (gameInfos[mid].vramHash2 < vramHash2) {
47 | start = mid + 1;
48 | } else {
49 | end = mid;
50 | }
51 | }
52 | return false;
53 | }
54 |
--------------------------------------------------------------------------------
/firmware/gamedb/game_detection.h:
--------------------------------------------------------------------------------
1 | #ifndef GBINTERCEPTOR_GAME_DETECTION
2 | #define GBINTERCEPTOR_GAME_DETECTION
3 |
4 | #include "pico/stdlib.h"
5 |
6 | #define BRANCH_BASED_FIX_LIST_SIZE 3
7 | #define DMA_REGISTER_MAP_SIZE 20
8 |
9 | typedef enum {nop, set, and, or, xor, sync} FixMethod;
10 |
11 | typedef struct {
12 | uint16_t jumpAddress;
13 | uint16_t fixTarget;
14 | FixMethod takenMethod;
15 | uint8_t takenValue;
16 | FixMethod notTakenMethod;
17 | uint8_t notTakenValue;
18 | } BranchBasedFix;
19 |
20 | typedef struct {
21 | uint vramHash1, vramHash2;
22 | uint16_t dmaFix; // Address that recognizes return after DMA (if not 0x0000)
23 | bool useImmediateIRQ; //Use vblank IRQ to sync the PPU even if it occured immediately after enabling interrupts, so it might have been delayed.
24 | bool disableStatSyncs; //Do not use stat register related tight loops for sync
25 | bool disableLySyncs; //Do not use stat register related tight loops for sync
26 | bool windowLineAlwaysPauses; //Used if window is disabled so close to the y=0 reset that we might miss that it has been enabled. In this case its internal counter still has to be initialized to zero so that its line counter actually pauses until the window is enabled again
27 | BranchBasedFix branchBasedFixes[BRANCH_BASED_FIX_LIST_SIZE]; //List of memory addresses of conditional jumps and how their branching behavior should set values in memory
28 | uint8_t writeRegistersDuringDMA[DMA_REGISTER_MAP_SIZE]; //Sequence of HRAM/IO addresses. Write the first to the second, the third to the fourth etc. during DMA
29 | char title[19];
30 | } GameInfo;
31 |
32 | extern volatile uint vramHash1, vramHash2;
33 | extern GameInfo gameInfo;
34 | extern volatile bool gameDetected;
35 |
36 | void resetHashes();
37 | bool detectGame();
38 |
39 | #define VRAM_HASH(ADDR, DATA) \
40 | if (memory[ADDR] != DATA) { \
41 | vramHash1 += ((ADDR << 8) | DATA); \
42 | vramHash2 += vramHash1; \
43 | }
44 |
45 | #endif
46 |
--------------------------------------------------------------------------------
/firmware/jpeg/base.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Staacks/gbinterceptor/14213d507fb056389c3be0bd35f8b57b359a52ea/firmware/jpeg/base.jpg
--------------------------------------------------------------------------------
/firmware/jpeg/base_no_chroma.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Staacks/gbinterceptor/14213d507fb056389c3be0bd35f8b57b359a52ea/firmware/jpeg/base_no_chroma.jpg
--------------------------------------------------------------------------------
/firmware/jpeg/generateBaseJpeg.py:
--------------------------------------------------------------------------------
1 | #This is a simple Python script that creates a JPEG file in the desired format and creates a header file with the data.
2 | #Note that I do not expect any changes here, so this is mostly here for reference and not properly automated.
3 | #If you change anything, you will very likely need to change offsets in the Pico code.
4 |
5 | def generateSOI():
6 | return bytearray([0xff, 0xd8])
7 |
8 | def generateAPP0():
9 | data = bytearray([0xff, 0xe0])
10 | data.extend([0x00, 0x10]) #length 16
11 | data.extend(b"JFIF\x00") #JFIF
12 | data.extend([0x01, 0x01]) #Version 1.1
13 | data.extend([0x01]) #Units DPI
14 | data.extend([0x00, 0x48, 0x00, 0x48]) #Density 72x72
15 | data.extend([0x00, 0x00]) #Thumbnail 0x0
16 | return data
17 |
18 | def generateAPP0NoChroma():
19 | data = bytearray([0xff, 0xe0])
20 | data.extend([0x00, 0x11]) #length 17
21 | data.extend(b"JFIF\x00") #JFIF
22 | data.extend([0x01, 0x01]) #Version 1.1
23 | data.extend([0x01]) #Units DPI
24 | data.extend([0x00, 0x48, 0x00, 0x48]) #Density 72x72
25 | data.extend([0x00, 0x00]) #Thumbnail 0x0
26 | data.extend([0x00]) #No meaning, we just need to pad it, so that the data block aligns with a multiple of 32bit
27 | return data
28 |
29 | def generateDQT(destination):
30 | data = bytearray([0xff, 0xdb])
31 | data.extend([0x00, 0x43]) #Length 67
32 | data.extend([destination]) #destination
33 | data.extend([0xff]*64) #QUantization table entirely filled with 0xff
34 | return data
35 |
36 | def generateSOF():
37 | data = bytearray([0xff, 0xc0])
38 | data.extend([0x00, 0x11]) #Length 17
39 | data.extend([0x08]) #Precision
40 | data.extend([0x04, 0x80]) #height 8*144
41 | data.extend([0x05, 0x00]) #width 8*160
42 | data.extend([0x03]) #Components
43 | data.extend([0x01, 0x22, 0x00]) #Luminance channel setup
44 | data.extend([0x02, 0x11, 0x00]) #Chrominance Cb channel setup
45 | data.extend([0x03, 0x11, 0x00]) #Chrominance Cr channel setup
46 | return data
47 |
48 | def generateSOFNoChroma():
49 | data = bytearray([0xff, 0xc0])
50 | data.extend([0x00, 0x0b]) #Length 11
51 | data.extend([0x08]) #Precision
52 | data.extend([0x04, 0x80]) #height 8*144
53 | data.extend([0x05, 0x00]) #width 8*160
54 | data.extend([0x01]) #Components
55 | data.extend([0x01, 0x22, 0x00]) #Luminance channel setup
56 | return data
57 |
58 | def generateDHT_DC():
59 | data = bytearray([0xff, 0xc4])
60 | data.extend([0x00, 0x17]) #Length 23
61 | data.extend([0x00]) #DC Huffman table, destination 0
62 |
63 | #Huffman table
64 | #0 -> 0x03
65 | #10 -> 0x02
66 | #110 -> 0x01
67 | #1110 -> 0x00
68 | data.extend([0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00])
69 |
70 | return data
71 |
72 | def generateDHT_DC_chrominance():
73 | data = bytearray([0xff, 0xc4])
74 | data.extend([0x00, 0x15]) #Length 21
75 | data.extend([0x01]) #DC Huffman table, destination 1
76 |
77 | #Huffman table
78 | #0 -> 0x00
79 | #10 -> 0x01
80 | data.extend([0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])
81 |
82 | return data
83 |
84 | def generateDHT_AC():
85 | data = bytearray([0xff, 0xc4])
86 | data.extend([0x00, 0x14]) #Length 20
87 | data.extend([0x10]) #AC Huffman table, destination 0
88 |
89 | #Huffman table
90 | data.extend([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
91 |
92 | return data
93 |
94 | def generateSOS():
95 | data = bytearray([0xff, 0xda])
96 | data.extend([0x00, 0x08]) #Length 8
97 | data.extend([0x01]) #Components: 1
98 | data.extend([0x01, 0x00]) #Component one uses tables 0/0
99 | data.extend([0x00, 0x3f]) #Spectral select
100 | data.extend([0x00]) #successive approx
101 | return data
102 |
103 | def generateSOS_chrominance():
104 | data = bytearray([0xff, 0xda])
105 | data.extend([0x00, 0x0a]) #Length 10
106 | data.extend([0x02]) #Components: 2
107 | data.extend([0x02, 0x10]) #Component two uses tables 1/0
108 | data.extend([0x03, 0x10]) #Component three uses tables 1/0
109 | data.extend([0x00, 0x3f]) #Spectral select
110 | data.extend([0x00]) #successive approx
111 | return data
112 |
113 | def encodeVal(v):
114 | lookup = [0b00000, 0b00010, 0b00100, 0b00110, 0b10000, 0b10010, 0b11000, 0b11100, 0b11010, 0b10100, 0b10110, 0b01000, 0b01010, 0b01100, 0b01110]
115 | return lookup[v+7]
116 |
117 | def generateData():
118 | last = 0
119 | pixelData = [0x00]*(160*144*5//8)
120 | for y in range(144):
121 | for x in range(160):
122 | lum = -3 if (x % 8 == 0 or y % 8 == 0) else -3+((x + y) % 8)
123 | diff = lum - last
124 | last = lum
125 | code = encodeVal(diff)
126 | offset = 5*(160*y+x)
127 | byteOffset = offset // 8
128 | bitOffset = offset % 8
129 | if bitOffset <= 3:
130 | pixelData[byteOffset] |= (code << (3-bitOffset))
131 | else:
132 | pixelData[byteOffset] |= (code >> (bitOffset-3))
133 | pixelData[byteOffset+1] |= ((code << (11-bitOffset)) & 0xff)
134 | return pixelData
135 |
136 | def generateData_chrominance():
137 | last = 0
138 | pixelData = [0x00]*(160*144*2*2//8//4+1) #160 * 144 pixels, 2 channels, one bit for DC and AC entry each, 8 bit per byte, 4 pixels per entry due to undersampling plus 1 byte that is not always used, but leaves headroom to set a DC offset in the beginning for green color mode
139 | #pixelData[0] = 0b10001000 #Set entire DC offset to make the image green
140 | return pixelData
141 |
142 | def generateEOI():
143 | return bytearray([0xff, 0xd9])
144 |
145 | def baseJpeg():
146 | data = bytearray()
147 | data.extend(generateSOI())
148 | data.extend(generateAPP0())
149 | data.extend(generateDQT(0x00)) #DQT
150 | data.extend(generateSOF())
151 | data.extend(generateDHT_DC())
152 | data.extend(generateDHT_DC_chrominance())
153 | data.extend(generateDHT_AC())
154 | data.extend(generateSOS())
155 | data.extend(generateData())
156 | data.extend(generateSOS_chrominance())
157 | data.extend(generateData_chrominance())
158 | data.extend(generateEOI())
159 | return data
160 |
161 | def baseJpegNoChroma():
162 | data = bytearray()
163 | data.extend(generateSOI())
164 | data.extend(generateAPP0NoChroma())
165 | data.extend(generateDQT(0x00)) #DQT
166 | data.extend(generateSOFNoChroma())
167 | data.extend(generateDHT_DC())
168 | data.extend(generateDHT_AC())
169 | data.extend(generateSOS())
170 | data.extend(generateData())
171 | data.extend(generateEOI())
172 | return data
173 |
174 | def generateFiles(jpg, h, var, data):
175 | with open(jpg, "wb") as f:
176 | f.write(data)
177 |
178 | with open(h, "w") as f:
179 | f.write("unsigned char __in_flash(\"jpeg\") " + var + "[] = {")
180 | for i in range(len(data)):
181 | if i % 16 == 0:
182 | f.write("\n")
183 | f.write(' 0x{:02x},'.format(data[i]))
184 | f.write("\n};\n")
185 |
186 | generateFiles("base.jpg", "base_jpeg.h", "base_jpeg", baseJpeg())
187 | generateFiles("base_no_chroma.jpg", "base_jpeg_no_chroma.h", "base_jpeg_no_chroma", baseJpegNoChroma())
188 |
--------------------------------------------------------------------------------
/firmware/jpeg/jpeg.h:
--------------------------------------------------------------------------------
1 | #ifndef GBINTERCEPTOR_JPEG
2 | #define GBINTERCEPTOR_JPEG
3 |
4 | #include "pico/stdlib.h"
5 | #include "ppu.h"
6 |
7 | #define JPEG_DATA_SIZE (SCREEN_SIZE * 5 / 8) //5bit per pixel, see https://github.com/Staacks/gbinterceptor/issues/17
8 | #define JPEG_HEADER_SIZE 188
9 | #define JPEG_HEADER_SIZE_NO_CHROMA 160
10 | #define JPEG_END_SIZE 2895
11 | #define JPEG_END_SIZE_NO_CHROMA 2
12 | #define FRAME_SIZE (JPEG_DATA_SIZE + JPEG_HEADER_SIZE + JPEG_END_SIZE)
13 | #define FRAME_SIZE_NO_CHROMA (JPEG_DATA_SIZE + JPEG_HEADER_SIZE_NO_CHROMA + JPEG_END_SIZE_NO_CHROMA)
14 |
15 | #define JPEG_CHROMA_OFFSET (JPEG_HEADER_SIZE + JPEG_DATA_SIZE + 12)
16 |
17 | void prepareJpegEncoding();
18 | void startBackbufferToJPEG(bool allowFrameBlend);
19 | void continueBackbufferToJPEG();
20 |
21 | #endif
--------------------------------------------------------------------------------
/firmware/jpeg/jpeg_encoding.pio:
--------------------------------------------------------------------------------
1 | .program jpegEncoding
2 |
3 | //The JPEG data uses a Huffman table that is designed such that every pixel can be
4 | //encoded in 5 bit (see https://github.com/Staacks/gbinterceptor/issues/17). This SM
5 | //takes bytes that contain differential values that have already been prepared to be
6 | //easily convertible to the Huffman encoding.
7 |
8 | //This is the second step in the encoding process:
9 | // jpeg_prepare -> jpeg_encoding
10 |
11 | //-7 => into SM: 0b0000 => encoded output: 0b 0 000 0
12 | //-6 => into SM: 0b0001 => encoded output: 0b 0 001 0
13 | //-5 => into SM: 0b0010 => encoded output: 0b 0 010 0
14 | //-4 => into SM: 0b0011 => encoded output: 0b 0 011 0
15 | //-3 => into SM: 0b0100 => encoded output: 0b 10 00 0
16 | //-2 => into SM: 0b0101 => encoded output: 0b 10 01 0
17 | //-1 => into SM: 0b0110 => encoded output: 0b 110 0 0
18 |
19 | // 0 => into SM: 0b1000 => encoded output: 0b 1110 0
20 | // 1 => into SM: 0b1001 => encoded output: 0b 110 1 0
21 | // 2 => into SM: 0b1010 => encoded output: 0b 10 10 0
22 | // 3 => into SM: 0b1011 => encoded output: 0b 10 11 0
23 | // 4 => into SM: 0b1100 => encoded output: 0b 0 100 0
24 | // 5 => into SM: 0b1101 => encoded output: 0b 0 101 0
25 | // 6 => into SM: 0b1110 => encoded output: 0b 0 110 0
26 | // 7 => into SM: 0b1111 => encoded output: 0b 0 111 0
27 |
28 | .wrap_target
29 | start:
30 | SET y 2 // Counter for remaining bits (-1)
31 |
32 | OUT x 1 //0: negative number, 1: positive number
33 | JMP !x negative
34 |
35 | ///POSITIVE///
36 | positive: //position of first one determines Huffman code
37 |
38 | codeP:
39 | OUT x 1
40 | JMP !x morecodeP
41 | IN null 1
42 | IN x 1
43 | JMP y-- remainder
44 | JMP end
45 |
46 | morecodeP:
47 | SET x 1
48 | IN x 1
49 | JMP y-- codeP
50 | IN null 1
51 | JMP end
52 |
53 |
54 |
55 | ///NEGATIVE///
56 | negative: //position of first zero determines Huffman code
57 |
58 | codeN:
59 | OUT x 1
60 | IN x 1
61 | JMP !x codeDoneN
62 | JMP y-- codeN
63 |
64 | codeDoneN:
65 | IN null 1
66 | JMP y-- remainder
67 | JMP end
68 |
69 |
70 | ///REMAINDER///
71 |
72 | remainder: //Remaining bits can just be added once the Huffman code has been chosen.
73 | OUT x 1
74 | IN x 1
75 | JMP y-- remainder
76 |
77 | ///END///
78 | end:
79 | IN null 1
80 |
81 | .wrap
82 |
83 | % c-sdk {
84 |
85 | void jpegEncoding_program_init(PIO pio, uint sm, uint offset) {
86 | pio_sm_config c = jpegEncoding_program_get_default_config(offset);
87 | sm_config_set_clkdiv(&c, 1);
88 |
89 | sm_config_set_in_shift(&c, false, true, 8);
90 | sm_config_set_out_shift(&c, false, true, 32);
91 |
92 | pio_sm_init(pio, sm, offset, &c);
93 | pio_sm_set_enabled(pio, sm, true);
94 | }
95 |
96 | %}
97 |
--------------------------------------------------------------------------------
/firmware/jpeg/jpeg_prepare.pio:
--------------------------------------------------------------------------------
1 | .program jpegPrepare
2 |
3 | //The JPEG data uses a Huffman table that is designed such that every pixel can be
4 | //encoded in 5 bit (see https://github.com/Staacks/gbinterceptor/issues/17). This SM
5 | //takes the differences plus eight from the CPU and subtracts one from the negative
6 | //values for easier encoding in the next step.
7 |
8 | //This is the first step in the encoding process:
9 | // jpeg_prepare -> jpeg_encoding
10 |
11 | //-7 => into SM: 0b00000001 => encoded output: 0b0000
12 | //-6 => into SM: 0b00000010 => encoded output: 0b0001
13 | //-5 => into SM: 0b00000011 => encoded output: 0b0010
14 | //-4 => into SM: 0b00000100 => encoded output: 0b0011
15 | //-3 => into SM: 0b00000101 => encoded output: 0b0100
16 | //-2 => into SM: 0b00000110 => encoded output: 0b0101
17 | //-1 => into SM: 0b00000111 => encoded output: 0b0110
18 | // Note that there is no 0b00000111 as output and no 0b00000000 as input, so we do not even have to treat edge cases
19 | // 0 => into SM: 0b00001000 => encoded output: 0b1000
20 | // 1 => into SM: 0b00001001 => encoded output: 0b1001
21 | // 2 => into SM: 0b00001010 => encoded output: 0b1010
22 | // 3 => into SM: 0b00001011 => encoded output: 0b1011
23 | // 4 => into SM: 0b00001100 => encoded output: 0b1100
24 | // 5 => into SM: 0b00001101 => encoded output: 0b1101
25 | // 6 => into SM: 0b00001110 => encoded output: 0b1110
26 | // 7 => into SM: 0b00001111 => encoded output: 0b1111
27 |
28 | .wrap_target
29 | start:
30 | //Incoming data is shifted to the RIGHT to counter endianess. Therefore...
31 | OUT y 3 //...we read the three least significant bits first
32 | OUT x 1 //...then read the sign...
33 | OUT null 4 //...and discard the zeros last.
34 |
35 | //Output happens in reverse order...
36 | IN x 1 //...by sending the sign first...
37 |
38 | JMP !x changed //...and then deciding if the...
39 |
40 | unchanged:
41 | JMP done //...least significant bits remain unchanged...
42 |
43 | changed:
44 | JMP y-- done //...or need to be decremented before...
45 |
46 | done:
47 | IN y 3 //...sending them.
48 |
49 | .wrap
50 |
51 | % c-sdk {
52 |
53 | void jpegPrepare_program_init(PIO pio, uint sm, uint offset) {
54 | pio_sm_config c = jpegPrepare_program_get_default_config(offset);
55 | sm_config_set_clkdiv(&c, 1);
56 |
57 | sm_config_set_in_shift(&c, false, true, 32);
58 | sm_config_set_out_shift(&c, true, true, 32);
59 |
60 | pio_sm_init(pio, sm, offset, &c);
61 | pio_sm_set_enabled(pio, sm, true);
62 | }
63 |
64 | %}
65 |
--------------------------------------------------------------------------------
/firmware/main.h:
--------------------------------------------------------------------------------
1 | #ifndef GBINTERCEPTOR_MAIN
2 | #define GBINTERCEPTOR_MAIN
3 |
4 | #define GBSENSE_PIN 0
5 |
6 | #define LED_SWITCH_PIN 1
7 | #define LED_PIN_MASK 0x02
8 |
9 | #define BASEVERSION "1.2.0"
10 |
11 | #ifdef BASE_VIDEO_MODE
12 | #define VERSION BASEVERSION "B"
13 | #else
14 | #define VERSION BASEVERSION
15 | #endif
16 |
17 | //On-screen display
18 | #define MODE_INFO_DURATION 100 //Duration of the mode info in frames
19 | #define GAME_DETECTED_INFO_DURATION 200 //Duration of the mode info in frames
20 |
21 | #endif
--------------------------------------------------------------------------------
/firmware/memory-bus.pio:
--------------------------------------------------------------------------------
1 | .program memoryBus
2 |
3 | .wrap_target
4 | wait 1 pin 28 ;As we set GPIO6 as pin0, CLK can be found on GPIO 2 which is now wrapped around to pin 28
5 | wait 0 pin 28 ;Wait for falling flank of CLK
6 | mov isr pins
7 | push
8 | .wrap
9 |
10 | % c-sdk {
11 |
12 | void memoryBus_program_init(PIO pio, uint sm, uint offset, float div) {
13 | pio_sm_config c = memoryBus_program_get_default_config(offset);
14 | sm_config_set_clkdiv(&c, div); //Clock
15 |
16 | //GPIO setup
17 | //See schematic: Pin 2 is CLK, followed by RD/WR/CS, then 16bit address and 8bit data
18 | //However, since we read all pins as 32bit, we want to allign the address to pin 6, so we get
19 | //0x0000ffff as address
20 | //0x00ff0000 as data
21 | //0xf0000000 containing the bits for CLK (should be 0 when reading), nWR, nRD and nCS
22 | //0x0f000000 containing garbage (our status LEDs and virtual padding GPIOs)
23 |
24 | sm_config_set_in_pins(&c, 6);
25 | pio_sm_set_consecutive_pindirs(pio, sm, 2, 28, false);
26 |
27 | sm_config_set_in_shift(&c, true, false, 32);
28 |
29 | pio_sm_init(pio, sm, offset, &c);
30 | pio_sm_set_enabled(pio, sm, true);
31 | }
32 |
33 | %}
--------------------------------------------------------------------------------
/firmware/opcodes.h:
--------------------------------------------------------------------------------
1 | #ifndef GBINTERCEPTOR_OPCODES
2 | #define GBINTERCEPTOR_OPCODES
3 |
4 | #include "pico/stdlib.h"
5 |
6 | extern void (*opcodes[])();
7 | void toMemory(uint16_t address, uint8_t data);
8 |
9 | #endif
--------------------------------------------------------------------------------
/firmware/osd.c:
--------------------------------------------------------------------------------
1 | #include "osd.h"
2 | #include "ppu.h"
3 | #include
4 |
5 | #include "font/font8x8_basic.h"
6 |
7 | uint8_t osdBuffer[OSD_HEIGHT * SCREEN_W];
8 |
9 | uint osdPosition = SCREEN_H;
10 | uint timeRemaining = 0;
11 |
12 | void inline renderOSDCharacter(char i, uint x, uint y, volatile uint8_t * targetBuffer, uint8_t fgColor, uint8_t bgColor) {
13 | for (uint yi = 0; yi < 8; yi++) {
14 | char line = font8x8_basic[i][yi];
15 | uint8_t volatile * bufferindex = targetBuffer + (y + yi) * SCREEN_W + x;
16 | uint8_t mask = 0x01;
17 | for (uint xi = 0; xi < 8; xi++) {
18 | if (line & mask)
19 | *bufferindex = fgColor;
20 | else
21 | *bufferindex = bgColor;
22 | mask <<= 1;
23 | bufferindex++;
24 | }
25 | }
26 | }
27 |
28 | void renderOSDFillLine(uint fromX, uint toX, uint y0, volatile uint8_t * targetBuffer, uint8_t color) {
29 | uint8_t volatile * bufferindex = targetBuffer + y0 * SCREEN_W + fromX;
30 | for (uint y = y0; y < y0 + 8; y++) {
31 | for (uint x = fromX; x < toX; x++) {
32 | *bufferindex = color;
33 | bufferindex++;
34 | }
35 | bufferindex += SCREEN_W - toX + fromX;
36 | }
37 | }
38 |
39 |
40 | void animateOSD() {
41 | if (timeRemaining) {
42 | timeRemaining--;
43 | if (timeRemaining < OSD_HEIGHT)
44 | osdPosition = SCREEN_H - timeRemaining;
45 | else if (osdPosition > SCREEN_H - OSD_HEIGHT)
46 | osdPosition--;
47 | }
48 | }
49 |
50 | uint renderText(const char * text, uint8_t fgColor, uint8_t bgColor, uint8_t * buffer, uint x, uint y) {
51 | const char *i = text;
52 | uint w = 0;
53 | uint xi = x;
54 | while (*i != '\0') {
55 | if (*i == '\n' || w >= 19) {
56 | y += 9;
57 | xi = x;
58 | w = 0;
59 | if (*i == '\n')
60 | i++;
61 | } else {
62 | renderOSDCharacter(*i, xi, y, buffer, fgColor, bgColor);
63 | xi += 8;
64 | w++;
65 | i++;
66 | }
67 | }
68 | return xi;
69 | }
70 |
71 | void renderOSD(const char * text, uint8_t fgColor, uint8_t bgColor, uint duration) {
72 | //Padding
73 | uint8_t volatile * topborder = osdBuffer;
74 | for (uint i = 0; i < SCREEN_W; i++) {
75 | *topborder = bgColor;
76 | topborder++;
77 | }
78 |
79 | //Text line
80 | uint x = 1;
81 | uint y = 1;
82 | renderOSDFillLine(0, x, y, osdBuffer, bgColor);
83 | x = renderText(text, fgColor, bgColor, osdBuffer, x, y);
84 | renderOSDFillLine(x, SCREEN_W, y, osdBuffer, bgColor);
85 |
86 | timeRemaining = duration;
87 | osdPosition = SCREEN_H;
88 | }
--------------------------------------------------------------------------------
/firmware/osd.h:
--------------------------------------------------------------------------------
1 | #ifndef OSD_H
2 | #define OSD_H
3 |
4 | #include "pico/stdlib.h"
5 |
6 | #define OSD_HEIGHT 9
7 |
8 | extern uint osdPosition;
9 | extern uint8_t osdBuffer[];
10 |
11 | void animateOSD();
12 | uint renderText(const char * text, uint8_t fgColor, uint8_t bgColor, uint8_t * buffer, uint x, uint y);
13 | void renderOSD(const char * text, uint8_t fgColor, uint8_t bgColor, uint duration);
14 |
15 | #endif
--------------------------------------------------------------------------------
/firmware/pico_sdk_import.cmake:
--------------------------------------------------------------------------------
1 | # This is a copy of /external/pico_sdk_import.cmake
2 |
3 | # This can be dropped into an external project to help locate this SDK
4 | # It should be include()ed prior to project()
5 |
6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
9 | endif ()
10 |
11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
14 | endif ()
15 |
16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
19 | endif ()
20 |
21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
24 |
25 | if (NOT PICO_SDK_PATH)
26 | if (PICO_SDK_FETCH_FROM_GIT)
27 | include(FetchContent)
28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
29 | if (PICO_SDK_FETCH_FROM_GIT_PATH)
30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
31 | endif ()
32 | # GIT_SUBMODULES_RECURSE was added in 3.17
33 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
34 | FetchContent_Declare(
35 | pico_sdk
36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
37 | GIT_TAG master
38 | GIT_SUBMODULES_RECURSE FALSE
39 | )
40 | else ()
41 | FetchContent_Declare(
42 | pico_sdk
43 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
44 | GIT_TAG master
45 | )
46 | endif ()
47 |
48 | if (NOT pico_sdk)
49 | message("Downloading Raspberry Pi Pico SDK")
50 | FetchContent_Populate(pico_sdk)
51 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
52 | endif ()
53 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
54 | else ()
55 | message(FATAL_ERROR
56 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
57 | )
58 | endif ()
59 | endif ()
60 |
61 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
62 | if (NOT EXISTS ${PICO_SDK_PATH})
63 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
64 | endif ()
65 |
66 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
67 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
68 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
69 | endif ()
70 |
71 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
72 |
73 | include(${PICO_SDK_INIT_CMAKE_FILE})
74 |
--------------------------------------------------------------------------------
/firmware/ppu.h:
--------------------------------------------------------------------------------
1 | #ifndef GBINTERCEPTOR_PPU
2 | #define GBINTERCEPTOR_PPU
3 |
4 | #include "pico/stdlib.h"
5 | #include "pico/sync.h"
6 | #include "jpeg/jpeg.h"
7 |
8 | #define SCREEN_W 160
9 | #define SCREEN_H 144
10 | #define SCREEN_SIZE (SCREEN_W * SCREEN_H)
11 |
12 | #define CYCLES_PER_FRAME 17556
13 | #define CYCLES_PER_LINE 114
14 | #define CYCLES_MODE_0 51 //max, actually 87 to 204 dots
15 | #define CYCLES_MODE_1 1140
16 | #define CYCLES_MODE_2 20
17 | #define CYCLES_MODE_3 43 //min, actually 172 to 289 dots
18 | #define CYCLES_LATEST_HBLANK (CYCLES_PER_LINE - 21) //At that point we are certainly in hblank
19 | #define LINES 154
20 |
21 | bool swapFrontbuffer();
22 | void ppuInit();
23 | void ppuStep(uint advance);
24 |
25 | extern uint8_t volatile * frontBuffer;
26 | extern uint8_t volatile * readyBuffer;
27 | extern uint8_t volatile * backBuffer;
28 | extern uint8_t volatile * lastBuffer;
29 |
30 | extern bool frameBlending;
31 |
32 | enum RenderState {done = 0, start, oamSearchDone, rendering};
33 | extern volatile enum RenderState renderState;
34 |
35 | extern uint lineCycle;
36 | extern int y;
37 |
38 | extern bool readyBufferIsNew;
39 | extern int volatile vblankOffset;
40 |
41 | extern volatile bool windowTileMap9C00;
42 | extern volatile bool windowEnable;
43 | extern volatile bool tileData8000;
44 | extern volatile bool bgTileMap9C00;
45 | extern volatile uint objSize;
46 | extern volatile bool objEnable;
47 | extern volatile bool bgAndWindowDisplay;
48 | extern volatile bool lcdAndPpuEnable;
49 |
50 | extern volatile uint8_t paletteBG[];
51 | extern volatile uint8_t paletteOBP0[];
52 | extern volatile uint8_t paletteOBP1[];
53 |
54 | struct __attribute__((__packed__)) SpriteAttribute {
55 | uint8_t y;
56 | uint8_t x;
57 | uint8_t tileIndex;
58 | uint8_t attributes;
59 | };
60 |
61 | #endif
--------------------------------------------------------------------------------
/firmware/screens/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Staacks/gbinterceptor/14213d507fb056389c3be0bd35f8b57b359a52ea/firmware/screens/default.png
--------------------------------------------------------------------------------
/firmware/screens/default.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
99 |
--------------------------------------------------------------------------------
/firmware/screens/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Staacks/gbinterceptor/14213d507fb056389c3be0bd35f8b57b359a52ea/firmware/screens/error.png
--------------------------------------------------------------------------------
/firmware/screens/error.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
94 |
--------------------------------------------------------------------------------
/firmware/screens/off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Staacks/gbinterceptor/14213d507fb056389c3be0bd35f8b57b359a52ea/firmware/screens/off.png
--------------------------------------------------------------------------------
/firmware/screens/off.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
111 |
--------------------------------------------------------------------------------
/firmware/screens/png2header.sh:
--------------------------------------------------------------------------------
1 | BASE=`echo $1 | cut -f 1 -d '.'`
2 | convert $1 -brightness-contrast -98x-98 gray:$BASE.raw
3 |
4 | xxd -i $BASE.raw | sed '1 s/unsigned char/unsigned char __in_flash("screens")/' > ${BASE}.h
5 | rm $BASE.raw
6 |
7 |
--------------------------------------------------------------------------------
/firmware/tusb_config.h:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2019 Ha Thach (tinyusb.org)
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | *
24 | */
25 |
26 | #ifndef _TUSB_CONFIG_H_
27 | #define _TUSB_CONFIG_H_
28 |
29 | #ifdef __cplusplus
30 | extern "C" {
31 | #endif
32 |
33 | //--------------------------------------------------------------------+
34 | // Board Specific Configuration
35 | //--------------------------------------------------------------------+
36 |
37 | // RHPort number used for device can be defined by board.mk, default to port 0
38 | #ifndef BOARD_TUD_RHPORT
39 | #define BOARD_TUD_RHPORT 0
40 | #endif
41 |
42 | // RHPort max operational speed can defined by board.mk
43 | #ifndef BOARD_TUD_MAX_SPEED
44 | #define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED
45 | #endif
46 |
47 | //--------------------------------------------------------------------
48 | // Common Configuration
49 | //--------------------------------------------------------------------
50 |
51 | // defined by compiler flags for flexibility
52 | #ifndef CFG_TUSB_MCU
53 | #error CFG_TUSB_MCU must be defined
54 | #endif
55 |
56 | #ifndef CFG_TUSB_OS
57 | #define CFG_TUSB_OS OPT_OS_NONE
58 | #endif
59 |
60 | #ifndef CFG_TUSB_DEBUG
61 | #define CFG_TUSB_DEBUG 0
62 | #endif
63 |
64 | // Enable Device stack
65 | #define CFG_TUD_ENABLED 1
66 |
67 | // Default is max speed that hardware controller could support with on-chip PHY
68 | #define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED
69 |
70 | /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
71 | * Tinyusb use follows macros to declare transferring memory so that they can be put
72 | * into those specific section.
73 | * e.g
74 | * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
75 | * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
76 | */
77 | #ifndef CFG_TUSB_MEM_SECTION
78 | #define CFG_TUSB_MEM_SECTION
79 | #endif
80 |
81 | #ifndef CFG_TUSB_MEM_ALIGN
82 | #define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4)))
83 | #endif
84 |
85 | //--------------------------------------------------------------------
86 | // DEVICE CONFIGURATION
87 | //--------------------------------------------------------------------
88 |
89 | #ifndef CFG_TUD_ENDPOINT0_SIZE
90 | #define CFG_TUD_ENDPOINT0_SIZE 64
91 | #endif
92 |
93 | //------------- CLASS -------------//
94 | // The number of video control interfaces
95 | #define CFG_TUD_VIDEO 1
96 | #define CFG_TUD_CDC 0
97 | #define CFG_TUD_MSC 0
98 | #define CFG_TUD_HID 0
99 | #define CFG_TUD_MIDI 0
100 | #define CFG_TUD_VENDOR 0
101 |
102 | // The number of video streaming interfaces
103 | #define CFG_TUD_VIDEO_STREAMING 1
104 |
105 | //Bulk transfer mode (in contrast to isochronous mode)
106 | #define CFG_TUD_VIDEO_STREAMING_BULK 1
107 |
108 | // video streaming endpoint size
109 | #if CFG_TUD_VIDEO_STREAMING_BULK == 1
110 | #define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 2048
111 | #else
112 | #define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 1023
113 | #endif
114 |
115 | #define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
116 | #define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
117 |
118 | // CDC Endpoint transfer buffer size, more is faster
119 | #define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
120 |
121 | #ifdef __cplusplus
122 | }
123 | #endif
124 |
125 | #endif /* _TUSB_CONFIG_H_ */
--------------------------------------------------------------------------------
/firmware/usb_descriptors.c:
--------------------------------------------------------------------------------
1 | #include "tusb.h"
2 | #include "usb_descriptors.h"
3 | #include "pico/unique_id.h"
4 |
5 | /* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug.
6 | * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC.
7 | *
8 | * Auto ProductID layout's Bitmap:
9 | * [MSB] VIDEO | AUDIO | MIDI | HID | MSC | CDC [LSB]
10 | */
11 | #define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) )
12 | #define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \
13 | _PID_MAP(MIDI, 3) | _PID_MAP(AUDIO, 4) | _PID_MAP(VIDEO, 5) | _PID_MAP(VENDOR, 6) )
14 |
15 | //--------------------------------------------------------------------+
16 | // Device Descriptors
17 | //--------------------------------------------------------------------+
18 | tusb_desc_device_t const desc_device = {
19 | .bLength = sizeof(tusb_desc_device_t),
20 | .bDescriptorType = TUSB_DESC_DEVICE,
21 | .bcdUSB = 0x0200,
22 |
23 | // Use Interface Association Descriptor (IAD) for Video
24 | // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1)
25 | .bDeviceClass = TUSB_CLASS_MISC,
26 | .bDeviceSubClass = MISC_SUBCLASS_COMMON,
27 | .bDeviceProtocol = MISC_PROTOCOL_IAD,
28 |
29 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
30 |
31 | .idVendor = 0xCafe,
32 | .idProduct = USB_PID,
33 | .bcdDevice = 0x0100,
34 |
35 | .iManufacturer = 0x01,
36 | .iProduct = 0x02,
37 | .iSerialNumber = 0x03,
38 |
39 | .bNumConfigurations = 0x01
40 | };
41 |
42 | // Invoked when received GET DEVICE DESCRIPTOR
43 | // Application return pointer to descriptor
44 | uint8_t const * tud_descriptor_device_cb(void) {
45 | return (uint8_t const *) &desc_device;
46 | }
47 |
48 | //--------------------------------------------------------------------+
49 | // Configuration Descriptor
50 | //--------------------------------------------------------------------+
51 |
52 | #if CFG_TUD_VIDEO_STREAMING_BULK == 1
53 | #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_VIDEO_CAPTURE_DESC_BULK_LEN + CFG_TUD_CDC*TUD_CDC_DESC_LEN )
54 | #else
55 | #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_VIDEO_CAPTURE_DESC_LEN + CFG_TUD_CDC*TUD_CDC_DESC_LEN )
56 | #endif
57 |
58 | #define EPNUM_VIDEO_IN 0x81
59 |
60 | #define EPNUM_CDC_NOTIF 0x83
61 | #define EPNUM_CDC_OUT 0x02
62 | #define EPNUM_CDC_IN 0x82
63 |
64 | uint8_t const desc_fs_configuration[] = {
65 | // Config number, interface count, string index, total length, attribute, power in mA
66 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0, 500),
67 | // IAD for Video Control
68 | #if CFG_TUD_VIDEO_STREAMING_BULK == 1
69 | TUD_VIDEO_CAPTURE_DESCRIPTOR_BULK(4, EPNUM_VIDEO_IN,
70 | FRAME_WIDTH, FRAME_HEIGHT,
71 | 64),
72 | #else
73 | TUD_VIDEO_CAPTURE_DESCRIPTOR(4, EPNUM_VIDEO_IN,
74 | FRAME_WIDTH, FRAME_HEIGHT,
75 | CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE),
76 | #endif
77 | // Interface number, string index, EP notification address and size, EP data address (out, in) and size.
78 | #if CFG_TUD_CDC == 1
79 | TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64),
80 | #endif
81 | };
82 |
83 | // Invoked when received GET CONFIGURATION DESCRIPTOR
84 | // Application return pointer to descriptor
85 | // Descriptor contents must exist long enough for transfer to complete
86 | uint8_t const * tud_descriptor_configuration_cb(uint8_t index) {
87 | (void) index; // for multiple configurations
88 | return desc_fs_configuration;
89 | }
90 |
91 | //--------------------------------------------------------------------+
92 | // String Descriptors
93 | //--------------------------------------------------------------------+
94 |
95 | // array of pointer to string descriptors
96 | char * string_desc_arr [] =
97 | {
98 | (char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
99 | "there.oughta.be", // 1: Manufacturer
100 | "GB Interceptor", // 2: Product
101 | "1234567890123456", // 3: Serials, should use chip ID
102 | "GB Interceptor Video", // 4: UVC Interface
103 | };
104 |
105 | static uint16_t _desc_str[32];
106 |
107 | void setUniqueSerial() {
108 | pico_get_unique_board_id_string(string_desc_arr[3], 17);
109 | }
110 |
111 | // Invoked when received GET STRING DESCRIPTOR request
112 | // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
113 | uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
114 | (void) langid;
115 |
116 | uint8_t chr_count;
117 |
118 | if ( index == 0) {
119 | memcpy(&_desc_str[1], string_desc_arr[0], 2);
120 | chr_count = 1;
121 | } else {
122 | // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
123 | // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
124 |
125 | if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL;
126 |
127 | const char* str = string_desc_arr[index];
128 |
129 | // Cap at max char
130 | chr_count = (uint8_t) strlen(str);
131 | if ( chr_count > 31 ) chr_count = 31;
132 |
133 | // Convert ASCII string into UTF-16
134 | for(uint8_t i=0; i