├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── .kitspace ├── bom.csv ├── copper_bottom.gbr ├── copper_top.gbr ├── drill_1_16.xln ├── gerber_job.gbrjob ├── profile.gbr ├── silkscreen_bottom.gbr ├── silkscreen_top.gbr ├── soldermask_bottom.gbr ├── soldermask_top.gbr ├── solderpaste_bottom.gbr └── solderpaste_top.gbr ├── COMPILE.md ├── INSTALL.md ├── LICENSE ├── README.md ├── USAGE.md ├── hw ├── README.md ├── schematic.png ├── usb64.brd └── usb64.sch ├── images ├── astick.gif ├── cont_benchmark.png ├── debug.png ├── dual_goldeneye.png ├── dual_perfectdark.png ├── install_advanced.png ├── install_advanced.svg ├── install_basic.png ├── install_setup.png ├── install_setup.svg ├── mouse_1.png ├── mouse_2.png ├── pcb_breakout.jpg ├── pcb_render.png ├── setup.jpg ├── silver.gif ├── tft_1.png ├── tft_2.png ├── tpak_1.png ├── tpak_5.png ├── tpak_6.png ├── tpak_7.png ├── tpak_8.png ├── usb64.jpg ├── usb64_logo.png ├── vp_cont.png ├── vp_info1.png ├── vp_main.png ├── vp_perfectdark.png └── vp_tpak.png ├── kitspace.yaml ├── platformio.ini └── src ├── analog_stick.cpp ├── analog_stick.h ├── fileio.cpp ├── fileio.h ├── input.cpp ├── input.h ├── main.cpp ├── memory.cpp ├── memory.h ├── n64 ├── n64_controller.c ├── n64_controller.h ├── n64_mempak.c ├── n64_mempak.h ├── n64_rumblepak.c ├── n64_rumblepak.h ├── n64_settings.c ├── n64_settings.h ├── n64_transferpak_gbcarts.c ├── n64_transferpak_gbcarts.h ├── n64_virtualpak.c └── n64_virtualpak.h ├── n64_wrapper.cpp ├── n64_wrapper.h ├── tft ├── controller_icon.h ├── tft.cpp ├── tft.h └── usb64_logo.h └── usb64_conf.h /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | - dev 8 | push: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | build: 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | BUILD_TAG: 0 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout repo 22 | uses: actions/checkout@v1 23 | with: 24 | submodules: recursive 25 | 26 | - name: Set up Python 27 | uses: actions/setup-python@v1 28 | 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install wheel 33 | pip install platformio 34 | export BUILD_TAG=build-$(date -u +'%Y%m%d%H%M') 35 | echo "BUILD_TAG=$BUILD_TAG" >> $GITHUB_ENV 36 | 37 | - name: Compile code 38 | run: platformio run -e teensy41 39 | 40 | - if: github.event_name == 'push' 41 | name: Create Release 42 | id: create_release 43 | uses: "actions/create-release@v1" 44 | with: 45 | tag_name: ${{ env.BUILD_TAG }} 46 | release_name: ${{ env.BUILD_TAG }} 47 | draft: false 48 | prerelease: false 49 | 50 | - if: github.event_name == 'push' 51 | name: Upload binary to release 52 | id: upload-release-asset 53 | uses: actions/upload-release-asset@v1 54 | with: 55 | upload_url: ${{ steps.create_release.outputs.upload_url }} 56 | asset_path: .pio/build/teensy41/firmware.hex 57 | asset_name: firmware-teensy41.hex 58 | asset_content_type: application/hex 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode 3 | include 4 | test -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/printf"] 2 | path = src/printf 3 | url = https://github.com/mpaland/printf 4 | 5 | [submodule "src/USBHost_t36"] 6 | path = src/USBHost_t36 7 | url = https://github.com/Ryzee119/USBHost_t36.git 8 | 9 | [submodule "src/ILI9341_t3n"] 10 | path = src/ILI9341_t3n 11 | url = https://github.com/KurtE/ILI9341_t3n.git 12 | -------------------------------------------------------------------------------- /.kitspace/bom.csv: -------------------------------------------------------------------------------- 1 | Reference,Qty,Description,Part Number,Manufacturer,Digikey 2 | C1,1,"CAP 100UF, 5MM DIA, 2MM LEAD",860010372006,Wurth Elektronik,732-8598-1-ND 3 | "C2,C3,C4",3,"CAP 100NF, 0805",885012207016,Wurth Elektronik,732-8026-1-ND 4 | "TFT1,TFT2",2,"FEMALE HEADER 0.1""",PPTC201LFBN-RC,Sullins Connector Solutions,S7018-ND 5 | USB1,1,USB TYPE A RECEPTACLE,USB-A1HSW6,On Shore Technology Inc,ED2989-ND 6 | "C5,C6,C7,C8",5,"CAP 100PF, 0805",885012207054,Wurth Elektronik,732-8046-1-ND -------------------------------------------------------------------------------- /.kitspace/drill_1_16.xln: -------------------------------------------------------------------------------- 1 | M48 2 | ;GenerationSoftware,Autodesk,EAGLE,9.6.2*% 3 | ;CreationDate,2022-08-08T06:10:21Z*% 4 | FMAT,2 5 | ICI,OFF 6 | METRIC,TZ,000.000 7 | T6C0.800 8 | T5C0.813 9 | T4C0.920 10 | T3C1.016 11 | T2C2.300 12 | T1C3.100 13 | % 14 | G90 15 | M71 16 | T1 17 | X64290Y58000 18 | X4290Y4000 19 | X64290Y4000 20 | X4290Y58000 21 | T2 22 | X15655Y8245 23 | X28795Y8245 24 | T3 25 | X36830Y57785 26 | X2540Y14970 27 | X66040Y9890 28 | X66040Y12430 29 | X66040Y14970 30 | X66040Y17510 31 | X66040Y20050 32 | X66040Y22590 33 | X66040Y27670 34 | X66040Y30210 35 | X39880Y53975 36 | X52070Y57785 37 | X39880Y51435 38 | X39880Y48895 39 | X39880Y46355 40 | X36830Y60325 41 | X39880Y43815 42 | X36830Y55245 43 | X36830Y52705 44 | X36830Y50165 45 | X36830Y47625 46 | X36830Y45085 47 | X36830Y42545 48 | X36830Y40005 49 | X36830Y37465 50 | X36830Y34925 51 | X36830Y32385 52 | X36830Y29845 53 | X36830Y27305 54 | X36830Y24765 55 | X36830Y22225 56 | X36830Y19685 57 | X36830Y17145 58 | X36830Y14605 59 | X36830Y12065 60 | X36830Y9525 61 | X36830Y6985 62 | X36830Y4445 63 | X36830Y1905 64 | X52070Y60325 65 | X66040Y25130 66 | X52070Y55245 67 | X52070Y52705 68 | X52070Y50165 69 | X52070Y47625 70 | X52070Y45085 71 | X52070Y42545 72 | X52070Y40005 73 | X52070Y37465 74 | X52070Y34925 75 | X52070Y32385 76 | X52070Y29845 77 | X52070Y27305 78 | X52070Y24765 79 | X52070Y22225 80 | X52070Y19685 81 | X52070Y17145 82 | X52070Y14605 83 | X52070Y12065 84 | X52070Y9525 85 | X52070Y6985 86 | X52070Y4445 87 | X52070Y1905 88 | X2540Y25130 89 | X2540Y22590 90 | X2540Y20050 91 | X2540Y17510 92 | T4 93 | X18725Y10955 94 | X21225Y10955 95 | X23225Y10955 96 | X25725Y10955 97 | T5 98 | X58420Y57404 99 | X58420Y59436 100 | T6 101 | X16510Y41910 102 | X25400Y41910 103 | X34290Y41275 104 | X60960Y25400 105 | X60325Y9525 106 | X60325Y6985 107 | X60325Y4445 108 | X60325Y1905 109 | X45085Y9525 110 | X43180Y38735 111 | X45720Y6985 112 | X44614Y21590 113 | X7620Y41910 114 | M30 -------------------------------------------------------------------------------- /.kitspace/gerber_job.gbrjob: -------------------------------------------------------------------------------- 1 | { 2 | "Header": { 3 | "Comment": "All values are metric (mm)", 4 | "CreationDate": "2022-08-08T06:10:21Z", 5 | "GenerationSoftware": { 6 | "Application": "EAGLE", 7 | "Vendor": "Autodesk", 8 | "Version": "9.6.2" 9 | }, 10 | "Part": "Single" 11 | }, 12 | "Overall": { 13 | "BoardThickness": 1.57, 14 | "LayerNumber": 2, 15 | "Name": { 16 | "ProjectId": "usb64" 17 | }, 18 | "Owner": "Wendland Wendland ", 19 | "Size": { 20 | "X": 68.56, 21 | "Y": 62.535 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.kitspace/profile.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %IN*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | %ADD10C,0.254000*% 12 | 13 | 14 | D10* 15 | X0Y0D02* 16 | X685600Y0D01* 17 | X685600Y575350D01* 18 | X685410Y579708D01* 19 | X684840Y584032D01* 20 | X683896Y588291D01* 21 | X682585Y592451D01* 22 | X680915Y596481D01* 23 | X678901Y600350D01* 24 | X676558Y604029D01* 25 | X673902Y607489D01* 26 | X670955Y610705D01* 27 | X667739Y613652D01* 28 | X664279Y616308D01* 29 | X660600Y618651D01* 30 | X656731Y620665D01* 31 | X652701Y622335D01* 32 | X648541Y623646D01* 33 | X644282Y624590D01* 34 | X639958Y625160D01* 35 | X635600Y625350D01* 36 | X50000Y625350D01* 37 | X45642Y625160D01* 38 | X41318Y624590D01* 39 | X37059Y623646D01* 40 | X32899Y622335D01* 41 | X28869Y620665D01* 42 | X25000Y618651D01* 43 | X21321Y616308D01* 44 | X17861Y613652D01* 45 | X14645Y610705D01* 46 | X11698Y607489D01* 47 | X9042Y604029D01* 48 | X6699Y600350D01* 49 | X4685Y596481D01* 50 | X3015Y592451D01* 51 | X1704Y588291D01* 52 | X760Y584032D01* 53 | X190Y579708D01* 54 | X0Y575350D01* 55 | X0Y0D01* 56 | X58400Y39446D02* 57 | X58321Y38342D01* 58 | X58163Y37246D01* 59 | X57928Y36164D01* 60 | X57616Y35102D01* 61 | X57229Y34065D01* 62 | X56769Y33058D01* 63 | X56239Y32086D01* 64 | X55640Y31154D01* 65 | X54977Y30268D01* 66 | X54252Y29431D01* 67 | X53469Y28648D01* 68 | X52632Y27923D01* 69 | X51746Y27260D01* 70 | X50814Y26661D01* 71 | X49843Y26131D01* 72 | X48835Y25671D01* 73 | X47798Y25284D01* 74 | X46736Y24972D01* 75 | X45654Y24737D01* 76 | X44558Y24579D01* 77 | X43454Y24500D01* 78 | X42346Y24500D01* 79 | X41242Y24579D01* 80 | X40146Y24737D01* 81 | X39064Y24972D01* 82 | X38002Y25284D01* 83 | X36965Y25671D01* 84 | X35958Y26131D01* 85 | X34986Y26661D01* 86 | X34054Y27260D01* 87 | X33168Y27923D01* 88 | X32331Y28648D01* 89 | X31548Y29431D01* 90 | X30823Y30268D01* 91 | X30160Y31154D01* 92 | X29561Y32086D01* 93 | X29031Y33058D01* 94 | X28571Y34065D01* 95 | X28184Y35102D01* 96 | X27872Y36164D01* 97 | X27637Y37246D01* 98 | X27479Y38342D01* 99 | X27400Y39446D01* 100 | X27400Y40554D01* 101 | X27479Y41658D01* 102 | X27637Y42754D01* 103 | X27872Y43836D01* 104 | X28184Y44898D01* 105 | X28571Y45935D01* 106 | X29031Y46943D01* 107 | X29561Y47914D01* 108 | X30160Y48846D01* 109 | X30823Y49732D01* 110 | X31548Y50569D01* 111 | X32331Y51352D01* 112 | X33168Y52077D01* 113 | X34054Y52740D01* 114 | X34986Y53339D01* 115 | X35958Y53869D01* 116 | X36965Y54329D01* 117 | X38002Y54716D01* 118 | X39064Y55028D01* 119 | X40146Y55263D01* 120 | X41242Y55421D01* 121 | X42346Y55500D01* 122 | X43454Y55500D01* 123 | X44558Y55421D01* 124 | X45654Y55263D01* 125 | X46736Y55028D01* 126 | X47798Y54716D01* 127 | X48835Y54329D01* 128 | X49843Y53869D01* 129 | X50814Y53339D01* 130 | X51746Y52740D01* 131 | X52632Y52077D01* 132 | X53469Y51352D01* 133 | X54252Y50569D01* 134 | X54977Y49732D01* 135 | X55640Y48846D01* 136 | X56239Y47914D01* 137 | X56769Y46943D01* 138 | X57229Y45935D01* 139 | X57616Y44898D01* 140 | X57928Y43836D01* 141 | X58163Y42754D01* 142 | X58321Y41658D01* 143 | X58400Y40554D01* 144 | X58400Y39446D01* 145 | X658400Y39446D02* 146 | X658321Y38342D01* 147 | X658163Y37246D01* 148 | X657928Y36164D01* 149 | X657616Y35102D01* 150 | X657229Y34065D01* 151 | X656769Y33058D01* 152 | X656239Y32086D01* 153 | X655640Y31154D01* 154 | X654977Y30268D01* 155 | X654252Y29431D01* 156 | X653469Y28648D01* 157 | X652632Y27923D01* 158 | X651746Y27260D01* 159 | X650814Y26661D01* 160 | X649843Y26131D01* 161 | X648835Y25671D01* 162 | X647798Y25284D01* 163 | X646736Y24972D01* 164 | X645654Y24737D01* 165 | X644558Y24579D01* 166 | X643454Y24500D01* 167 | X642346Y24500D01* 168 | X641242Y24579D01* 169 | X640146Y24737D01* 170 | X639064Y24972D01* 171 | X638002Y25284D01* 172 | X636965Y25671D01* 173 | X635958Y26131D01* 174 | X634986Y26661D01* 175 | X634054Y27260D01* 176 | X633168Y27923D01* 177 | X632331Y28648D01* 178 | X631548Y29431D01* 179 | X630823Y30268D01* 180 | X630160Y31154D01* 181 | X629561Y32086D01* 182 | X629031Y33058D01* 183 | X628571Y34065D01* 184 | X628184Y35102D01* 185 | X627872Y36164D01* 186 | X627637Y37246D01* 187 | X627479Y38342D01* 188 | X627400Y39446D01* 189 | X627400Y40554D01* 190 | X627479Y41658D01* 191 | X627637Y42754D01* 192 | X627872Y43836D01* 193 | X628184Y44898D01* 194 | X628571Y45935D01* 195 | X629031Y46943D01* 196 | X629561Y47914D01* 197 | X630160Y48846D01* 198 | X630823Y49732D01* 199 | X631548Y50569D01* 200 | X632331Y51352D01* 201 | X633168Y52077D01* 202 | X634054Y52740D01* 203 | X634986Y53339D01* 204 | X635958Y53869D01* 205 | X636965Y54329D01* 206 | X638002Y54716D01* 207 | X639064Y55028D01* 208 | X640146Y55263D01* 209 | X641242Y55421D01* 210 | X642346Y55500D01* 211 | X643454Y55500D01* 212 | X644558Y55421D01* 213 | X645654Y55263D01* 214 | X646736Y55028D01* 215 | X647798Y54716D01* 216 | X648835Y54329D01* 217 | X649843Y53869D01* 218 | X650814Y53339D01* 219 | X651746Y52740D01* 220 | X652632Y52077D01* 221 | X653469Y51352D01* 222 | X654252Y50569D01* 223 | X654977Y49732D01* 224 | X655640Y48846D01* 225 | X656239Y47914D01* 226 | X656769Y46943D01* 227 | X657229Y45935D01* 228 | X657616Y44898D01* 229 | X657928Y43836D01* 230 | X658163Y42754D01* 231 | X658321Y41658D01* 232 | X658400Y40554D01* 233 | X658400Y39446D01* 234 | X58400Y579446D02* 235 | X58321Y578342D01* 236 | X58163Y577246D01* 237 | X57928Y576164D01* 238 | X57616Y575102D01* 239 | X57229Y574065D01* 240 | X56769Y573058D01* 241 | X56239Y572086D01* 242 | X55640Y571154D01* 243 | X54977Y570268D01* 244 | X54252Y569431D01* 245 | X53469Y568648D01* 246 | X52632Y567923D01* 247 | X51746Y567260D01* 248 | X50814Y566661D01* 249 | X49843Y566131D01* 250 | X48835Y565671D01* 251 | X47798Y565284D01* 252 | X46736Y564972D01* 253 | X45654Y564737D01* 254 | X44558Y564579D01* 255 | X43454Y564500D01* 256 | X42346Y564500D01* 257 | X41242Y564579D01* 258 | X40146Y564737D01* 259 | X39064Y564972D01* 260 | X38002Y565284D01* 261 | X36965Y565671D01* 262 | X35958Y566131D01* 263 | X34986Y566661D01* 264 | X34054Y567260D01* 265 | X33168Y567923D01* 266 | X32331Y568648D01* 267 | X31548Y569431D01* 268 | X30823Y570268D01* 269 | X30160Y571154D01* 270 | X29561Y572086D01* 271 | X29031Y573058D01* 272 | X28571Y574065D01* 273 | X28184Y575102D01* 274 | X27872Y576164D01* 275 | X27637Y577246D01* 276 | X27479Y578342D01* 277 | X27400Y579446D01* 278 | X27400Y580554D01* 279 | X27479Y581658D01* 280 | X27637Y582754D01* 281 | X27872Y583836D01* 282 | X28184Y584898D01* 283 | X28571Y585935D01* 284 | X29031Y586943D01* 285 | X29561Y587914D01* 286 | X30160Y588846D01* 287 | X30823Y589732D01* 288 | X31548Y590569D01* 289 | X32331Y591352D01* 290 | X33168Y592077D01* 291 | X34054Y592740D01* 292 | X34986Y593339D01* 293 | X35958Y593869D01* 294 | X36965Y594329D01* 295 | X38002Y594716D01* 296 | X39064Y595028D01* 297 | X40146Y595263D01* 298 | X41242Y595421D01* 299 | X42346Y595500D01* 300 | X43454Y595500D01* 301 | X44558Y595421D01* 302 | X45654Y595263D01* 303 | X46736Y595028D01* 304 | X47798Y594716D01* 305 | X48835Y594329D01* 306 | X49843Y593869D01* 307 | X50814Y593339D01* 308 | X51746Y592740D01* 309 | X52632Y592077D01* 310 | X53469Y591352D01* 311 | X54252Y590569D01* 312 | X54977Y589732D01* 313 | X55640Y588846D01* 314 | X56239Y587914D01* 315 | X56769Y586943D01* 316 | X57229Y585935D01* 317 | X57616Y584898D01* 318 | X57928Y583836D01* 319 | X58163Y582754D01* 320 | X58321Y581658D01* 321 | X58400Y580554D01* 322 | X58400Y579446D01* 323 | X658400Y579446D02* 324 | X658321Y578342D01* 325 | X658163Y577246D01* 326 | X657928Y576164D01* 327 | X657616Y575102D01* 328 | X657229Y574065D01* 329 | X656769Y573058D01* 330 | X656239Y572086D01* 331 | X655640Y571154D01* 332 | X654977Y570268D01* 333 | X654252Y569431D01* 334 | X653469Y568648D01* 335 | X652632Y567923D01* 336 | X651746Y567260D01* 337 | X650814Y566661D01* 338 | X649843Y566131D01* 339 | X648835Y565671D01* 340 | X647798Y565284D01* 341 | X646736Y564972D01* 342 | X645654Y564737D01* 343 | X644558Y564579D01* 344 | X643454Y564500D01* 345 | X642346Y564500D01* 346 | X641242Y564579D01* 347 | X640146Y564737D01* 348 | X639064Y564972D01* 349 | X638002Y565284D01* 350 | X636965Y565671D01* 351 | X635958Y566131D01* 352 | X634986Y566661D01* 353 | X634054Y567260D01* 354 | X633168Y567923D01* 355 | X632331Y568648D01* 356 | X631548Y569431D01* 357 | X630823Y570268D01* 358 | X630160Y571154D01* 359 | X629561Y572086D01* 360 | X629031Y573058D01* 361 | X628571Y574065D01* 362 | X628184Y575102D01* 363 | X627872Y576164D01* 364 | X627637Y577246D01* 365 | X627479Y578342D01* 366 | X627400Y579446D01* 367 | X627400Y580554D01* 368 | X627479Y581658D01* 369 | X627637Y582754D01* 370 | X627872Y583836D01* 371 | X628184Y584898D01* 372 | X628571Y585935D01* 373 | X629031Y586943D01* 374 | X629561Y587914D01* 375 | X630160Y588846D01* 376 | X630823Y589732D01* 377 | X631548Y590569D01* 378 | X632331Y591352D01* 379 | X633168Y592077D01* 380 | X634054Y592740D01* 381 | X634986Y593339D01* 382 | X635958Y593869D01* 383 | X636965Y594329D01* 384 | X638002Y594716D01* 385 | X639064Y595028D01* 386 | X640146Y595263D01* 387 | X641242Y595421D01* 388 | X642346Y595500D01* 389 | X643454Y595500D01* 390 | X644558Y595421D01* 391 | X645654Y595263D01* 392 | X646736Y595028D01* 393 | X647798Y594716D01* 394 | X648835Y594329D01* 395 | X649843Y593869D01* 396 | X650814Y593339D01* 397 | X651746Y592740D01* 398 | X652632Y592077D01* 399 | X653469Y591352D01* 400 | X654252Y590569D01* 401 | X654977Y589732D01* 402 | X655640Y588846D01* 403 | X656239Y587914D01* 404 | X656769Y586943D01* 405 | X657229Y585935D01* 406 | X657616Y584898D01* 407 | X657928Y583836D01* 408 | X658163Y582754D01* 409 | X658321Y581658D01* 410 | X658400Y580554D01* 411 | X658400Y579446D01* 412 | X168050Y81998D02* 413 | X167979Y81097D01* 414 | X167838Y80205D01* 415 | X167627Y79326D01* 416 | X167348Y78467D01* 417 | X167002Y77632D01* 418 | X166591Y76827D01* 419 | X166119Y76056D01* 420 | X165588Y75325D01* 421 | X165001Y74638D01* 422 | X164362Y73999D01* 423 | X163675Y73412D01* 424 | X162944Y72881D01* 425 | X162173Y72409D01* 426 | X161368Y71998D01* 427 | X160533Y71652D01* 428 | X159674Y71373D01* 429 | X158795Y71162D01* 430 | X157903Y71021D01* 431 | X157002Y70950D01* 432 | X156098Y70950D01* 433 | X155197Y71021D01* 434 | X154305Y71162D01* 435 | X153426Y71373D01* 436 | X152567Y71652D01* 437 | X151732Y71998D01* 438 | X150927Y72409D01* 439 | X150156Y72881D01* 440 | X149425Y73412D01* 441 | X148738Y73999D01* 442 | X148099Y74638D01* 443 | X147512Y75325D01* 444 | X146981Y76056D01* 445 | X146509Y76827D01* 446 | X146098Y77632D01* 447 | X145752Y78467D01* 448 | X145473Y79326D01* 449 | X145262Y80205D01* 450 | X145121Y81097D01* 451 | X145050Y81998D01* 452 | X145050Y82902D01* 453 | X145121Y83803D01* 454 | X145262Y84695D01* 455 | X145473Y85574D01* 456 | X145752Y86433D01* 457 | X146098Y87268D01* 458 | X146509Y88073D01* 459 | X146981Y88844D01* 460 | X147512Y89575D01* 461 | X148099Y90262D01* 462 | X148738Y90901D01* 463 | X149425Y91488D01* 464 | X150156Y92019D01* 465 | X150927Y92491D01* 466 | X151732Y92902D01* 467 | X152567Y93248D01* 468 | X153426Y93527D01* 469 | X154305Y93738D01* 470 | X155197Y93879D01* 471 | X156098Y93950D01* 472 | X157002Y93950D01* 473 | X157903Y93879D01* 474 | X158795Y93738D01* 475 | X159674Y93527D01* 476 | X160533Y93248D01* 477 | X161368Y92902D01* 478 | X162173Y92491D01* 479 | X162944Y92019D01* 480 | X163675Y91488D01* 481 | X164362Y90901D01* 482 | X165001Y90262D01* 483 | X165588Y89575D01* 484 | X166119Y88844D01* 485 | X166591Y88073D01* 486 | X167002Y87268D01* 487 | X167348Y86433D01* 488 | X167627Y85574D01* 489 | X167838Y84695D01* 490 | X167979Y83803D01* 491 | X168050Y82902D01* 492 | X168050Y81998D01* 493 | X299450Y81998D02* 494 | X299379Y81097D01* 495 | X299238Y80205D01* 496 | X299027Y79326D01* 497 | X298748Y78467D01* 498 | X298402Y77632D01* 499 | X297991Y76827D01* 500 | X297519Y76056D01* 501 | X296988Y75325D01* 502 | X296401Y74638D01* 503 | X295762Y73999D01* 504 | X295075Y73412D01* 505 | X294344Y72881D01* 506 | X293573Y72409D01* 507 | X292768Y71998D01* 508 | X291933Y71652D01* 509 | X291074Y71373D01* 510 | X290195Y71162D01* 511 | X289303Y71021D01* 512 | X288402Y70950D01* 513 | X287498Y70950D01* 514 | X286597Y71021D01* 515 | X285705Y71162D01* 516 | X284826Y71373D01* 517 | X283967Y71652D01* 518 | X283132Y71998D01* 519 | X282327Y72409D01* 520 | X281556Y72881D01* 521 | X280825Y73412D01* 522 | X280138Y73999D01* 523 | X279499Y74638D01* 524 | X278912Y75325D01* 525 | X278381Y76056D01* 526 | X277909Y76827D01* 527 | X277498Y77632D01* 528 | X277152Y78467D01* 529 | X276873Y79326D01* 530 | X276662Y80205D01* 531 | X276521Y81097D01* 532 | X276450Y81998D01* 533 | X276450Y82902D01* 534 | X276521Y83803D01* 535 | X276662Y84695D01* 536 | X276873Y85574D01* 537 | X277152Y86433D01* 538 | X277498Y87268D01* 539 | X277909Y88073D01* 540 | X278381Y88844D01* 541 | X278912Y89575D01* 542 | X279499Y90262D01* 543 | X280138Y90901D01* 544 | X280825Y91488D01* 545 | X281556Y92019D01* 546 | X282327Y92491D01* 547 | X283132Y92902D01* 548 | X283967Y93248D01* 549 | X284826Y93527D01* 550 | X285705Y93738D01* 551 | X286597Y93879D01* 552 | X287498Y93950D01* 553 | X288402Y93950D01* 554 | X289303Y93879D01* 555 | X290195Y93738D01* 556 | X291074Y93527D01* 557 | X291933Y93248D01* 558 | X292768Y92902D01* 559 | X293573Y92491D01* 560 | X294344Y92019D01* 561 | X295075Y91488D01* 562 | X295762Y90901D01* 563 | X296401Y90262D01* 564 | X296988Y89575D01* 565 | X297519Y88844D01* 566 | X297991Y88073D01* 567 | X298402Y87268D01* 568 | X298748Y86433D01* 569 | X299027Y85574D01* 570 | X299238Y84695D01* 571 | X299379Y83803D01* 572 | X299450Y82902D01* 573 | X299450Y81998D01* 574 | M02* 575 | -------------------------------------------------------------------------------- /.kitspace/silkscreen_bottom.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSilkscreen Bottom*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | 12 | 13 | M02* 14 | -------------------------------------------------------------------------------- /.kitspace/soldermask_bottom.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSoldermask Bottom*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | %ADD10C,3.201600*% 12 | %ADD11P,1.539592X8X292.500000*% 13 | %ADD12C,1.422400*% 14 | %ADD13C,1.625600*% 15 | %ADD14R,1.529600X1.529600*% 16 | %ADD15C,1.529600*% 17 | %ADD16C,2.401600*% 18 | 19 | 20 | D10* 21 | X42900Y40000D03* 22 | X642900Y40000D03* 23 | X42900Y580000D03* 24 | X642900Y580000D03* 25 | D11* 26 | X584200Y574040D03* 27 | D12* 28 | X584200Y594360D03* 29 | D13* 30 | X375920Y603250D02* 31 | X360680Y603250D01* 32 | X360680Y577850D02* 33 | X375920Y577850D01* 34 | X375920Y552450D02* 35 | X360680Y552450D01* 36 | X360680Y527050D02* 37 | X375920Y527050D01* 38 | X375920Y501650D02* 39 | X360680Y501650D01* 40 | X360680Y476250D02* 41 | X375920Y476250D01* 42 | X375920Y450850D02* 43 | X360680Y450850D01* 44 | X360680Y425450D02* 45 | X375920Y425450D01* 46 | X375920Y400050D02* 47 | X360680Y400050D01* 48 | X360680Y374650D02* 49 | X375920Y374650D01* 50 | X375920Y349250D02* 51 | X360680Y349250D01* 52 | X360680Y323850D02* 53 | X375920Y323850D01* 54 | X375920Y298450D02* 55 | X360680Y298450D01* 56 | X360680Y273050D02* 57 | X375920Y273050D01* 58 | X375920Y247650D02* 59 | X360680Y247650D01* 60 | X360680Y222250D02* 61 | X375920Y222250D01* 62 | X375920Y196850D02* 63 | X360680Y196850D01* 64 | X360680Y171450D02* 65 | X375920Y171450D01* 66 | X375920Y146050D02* 67 | X360680Y146050D01* 68 | X360680Y120650D02* 69 | X375920Y120650D01* 70 | X375920Y95250D02* 71 | X360680Y95250D01* 72 | X360680Y69850D02* 73 | X375920Y69850D01* 74 | X375920Y44450D02* 75 | X360680Y44450D01* 76 | X360680Y19050D02* 77 | X375920Y19050D01* 78 | X513080Y603250D02* 79 | X528320Y603250D01* 80 | X528320Y577850D02* 81 | X513080Y577850D01* 82 | X513080Y552450D02* 83 | X528320Y552450D01* 84 | X528320Y527050D02* 85 | X513080Y527050D01* 86 | X513080Y501650D02* 87 | X528320Y501650D01* 88 | X528320Y476250D02* 89 | X513080Y476250D01* 90 | X513080Y450850D02* 91 | X528320Y450850D01* 92 | X528320Y425450D02* 93 | X513080Y425450D01* 94 | X513080Y400050D02* 95 | X528320Y400050D01* 96 | X528320Y374650D02* 97 | X513080Y374650D01* 98 | X513080Y349250D02* 99 | X528320Y349250D01* 100 | X528320Y323850D02* 101 | X513080Y323850D01* 102 | X513080Y298450D02* 103 | X528320Y298450D01* 104 | X528320Y273050D02* 105 | X513080Y273050D01* 106 | X513080Y247650D02* 107 | X528320Y247650D01* 108 | X528320Y222250D02* 109 | X513080Y222250D01* 110 | X513080Y196850D02* 111 | X528320Y196850D01* 112 | X528320Y171450D02* 113 | X513080Y171450D01* 114 | X513080Y146050D02* 115 | X528320Y146050D01* 116 | X528320Y120650D02* 117 | X513080Y120650D01* 118 | X513080Y95250D02* 119 | X528320Y95250D01* 120 | X528320Y69850D02* 121 | X513080Y69850D01* 122 | X513080Y44450D02* 123 | X528320Y44450D01* 124 | X528320Y19050D02* 125 | X513080Y19050D01* 126 | X33020Y251300D02* 127 | X17780Y251300D01* 128 | X17780Y225900D02* 129 | X33020Y225900D01* 130 | X33020Y200500D02* 131 | X17780Y200500D01* 132 | X17780Y175100D02* 133 | X33020Y175100D01* 134 | X33020Y149700D02* 135 | X17780Y149700D01* 136 | X652780Y98900D02* 137 | X668020Y98900D01* 138 | X668020Y124300D02* 139 | X652780Y124300D01* 140 | X652780Y149700D02* 141 | X668020Y149700D01* 142 | X668020Y175100D02* 143 | X652780Y175100D01* 144 | X652780Y200500D02* 145 | X668020Y200500D01* 146 | X668020Y225900D02* 147 | X652780Y225900D01* 148 | X652780Y251300D02* 149 | X668020Y251300D01* 150 | X668020Y276700D02* 151 | X652780Y276700D01* 152 | X652780Y302100D02* 153 | X668020Y302100D01* 154 | X406420Y539750D02* 155 | X391180Y539750D01* 156 | X391180Y514350D02* 157 | X406420Y514350D01* 158 | X406420Y488950D02* 159 | X391180Y488950D01* 160 | X391180Y463550D02* 161 | X406420Y463550D01* 162 | X406420Y438150D02* 163 | X391180Y438150D01* 164 | D14* 165 | X187250Y109550D03* 166 | D15* 167 | X212250Y109550D03* 168 | X232250Y109550D03* 169 | X257250Y109550D03* 170 | D16* 171 | X156550Y82450D03* 172 | X287950Y82450D03* 173 | M02* 174 | -------------------------------------------------------------------------------- /.kitspace/soldermask_top.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSoldermask Top*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | %ADD10C,3.201600*% 12 | %ADD11P,1.539592X8X292.500000*% 13 | %ADD12C,1.422400*% 14 | %ADD13C,1.625600*% 15 | %ADD14R,1.371600X2.641600*% 16 | %ADD15R,1.701600X1.401600*% 17 | %ADD16R,1.529600X1.529600*% 18 | %ADD17C,1.529600*% 19 | %ADD18C,2.401600*% 20 | %ADD19R,1.401600X1.701600*% 21 | 22 | 23 | D10* 24 | X42900Y40000D03* 25 | X642900Y40000D03* 26 | X42900Y580000D03* 27 | X642900Y580000D03* 28 | D11* 29 | X584200Y574040D03* 30 | D12* 31 | X584200Y594360D03* 32 | D13* 33 | X375920Y603250D02* 34 | X360680Y603250D01* 35 | X360680Y577850D02* 36 | X375920Y577850D01* 37 | X375920Y552450D02* 38 | X360680Y552450D01* 39 | X360680Y527050D02* 40 | X375920Y527050D01* 41 | X375920Y501650D02* 42 | X360680Y501650D01* 43 | X360680Y476250D02* 44 | X375920Y476250D01* 45 | X375920Y450850D02* 46 | X360680Y450850D01* 47 | X360680Y425450D02* 48 | X375920Y425450D01* 49 | X375920Y400050D02* 50 | X360680Y400050D01* 51 | X360680Y374650D02* 52 | X375920Y374650D01* 53 | X375920Y349250D02* 54 | X360680Y349250D01* 55 | X360680Y323850D02* 56 | X375920Y323850D01* 57 | X375920Y298450D02* 58 | X360680Y298450D01* 59 | X360680Y273050D02* 60 | X375920Y273050D01* 61 | X375920Y247650D02* 62 | X360680Y247650D01* 63 | X360680Y222250D02* 64 | X375920Y222250D01* 65 | X375920Y196850D02* 66 | X360680Y196850D01* 67 | X360680Y171450D02* 68 | X375920Y171450D01* 69 | X375920Y146050D02* 70 | X360680Y146050D01* 71 | X360680Y120650D02* 72 | X375920Y120650D01* 73 | X375920Y95250D02* 74 | X360680Y95250D01* 75 | X360680Y69850D02* 76 | X375920Y69850D01* 77 | X375920Y44450D02* 78 | X360680Y44450D01* 79 | X360680Y19050D02* 80 | X375920Y19050D01* 81 | X513080Y603250D02* 82 | X528320Y603250D01* 83 | X528320Y577850D02* 84 | X513080Y577850D01* 85 | X513080Y552450D02* 86 | X528320Y552450D01* 87 | X528320Y527050D02* 88 | X513080Y527050D01* 89 | X513080Y501650D02* 90 | X528320Y501650D01* 91 | X528320Y476250D02* 92 | X513080Y476250D01* 93 | X513080Y450850D02* 94 | X528320Y450850D01* 95 | X528320Y425450D02* 96 | X513080Y425450D01* 97 | X513080Y400050D02* 98 | X528320Y400050D01* 99 | X528320Y374650D02* 100 | X513080Y374650D01* 101 | X513080Y349250D02* 102 | X528320Y349250D01* 103 | X528320Y323850D02* 104 | X513080Y323850D01* 105 | X513080Y298450D02* 106 | X528320Y298450D01* 107 | X528320Y273050D02* 108 | X513080Y273050D01* 109 | X513080Y247650D02* 110 | X528320Y247650D01* 111 | X528320Y222250D02* 112 | X513080Y222250D01* 113 | X513080Y196850D02* 114 | X528320Y196850D01* 115 | X528320Y171450D02* 116 | X513080Y171450D01* 117 | X513080Y146050D02* 118 | X528320Y146050D01* 119 | X528320Y120650D02* 120 | X513080Y120650D01* 121 | X513080Y95250D02* 122 | X528320Y95250D01* 123 | X528320Y69850D02* 124 | X513080Y69850D01* 125 | X513080Y44450D02* 126 | X528320Y44450D01* 127 | X528320Y19050D02* 128 | X513080Y19050D01* 129 | X33020Y251300D02* 130 | X17780Y251300D01* 131 | X17780Y225900D02* 132 | X33020Y225900D01* 133 | X33020Y200500D02* 134 | X17780Y200500D01* 135 | X17780Y175100D02* 136 | X33020Y175100D01* 137 | X33020Y149700D02* 138 | X17780Y149700D01* 139 | D14* 140 | X25400Y444500D03* 141 | X292100Y444500D03* 142 | X317500Y444500D03* 143 | X342900Y444500D03* 144 | X50800Y444500D03* 145 | X76200Y444500D03* 146 | X114300Y444500D03* 147 | X139700Y444500D03* 148 | X165100Y444500D03* 149 | X203200Y444500D03* 150 | X228600Y444500D03* 151 | X254000Y444500D03* 152 | D13* 153 | X652780Y98900D02* 154 | X668020Y98900D01* 155 | X668020Y124300D02* 156 | X652780Y124300D01* 157 | X652780Y149700D02* 158 | X668020Y149700D01* 159 | X668020Y175100D02* 160 | X652780Y175100D01* 161 | X652780Y200500D02* 162 | X668020Y200500D01* 163 | X668020Y225900D02* 164 | X652780Y225900D01* 165 | X652780Y251300D02* 166 | X668020Y251300D01* 167 | X668020Y276700D02* 168 | X652780Y276700D01* 169 | X652780Y302100D02* 170 | X668020Y302100D01* 171 | D15* 172 | X628650Y276700D03* 173 | X628650Y256700D03* 174 | X628650Y104300D03* 175 | X628650Y124300D03* 176 | X571500Y537050D03* 177 | X571500Y517050D03* 178 | D13* 179 | X406420Y539750D02* 180 | X391180Y539750D01* 181 | X391180Y514350D02* 182 | X406420Y514350D01* 183 | X406420Y488950D02* 184 | X391180Y488950D01* 185 | X391180Y463550D02* 186 | X406420Y463550D01* 187 | X406420Y438150D02* 188 | X391180Y438150D01* 189 | D16* 190 | X187250Y109550D03* 191 | D17* 192 | X212250Y109550D03* 193 | X232250Y109550D03* 194 | X257250Y109550D03* 195 | D18* 196 | X156550Y82450D03* 197 | X287950Y82450D03* 198 | D19* 199 | X561500Y95250D03* 200 | X581500Y95250D03* 201 | X561500Y69850D03* 202 | X581500Y69850D03* 203 | X561500Y44450D03* 204 | X581500Y44450D03* 205 | X561500Y19050D03* 206 | X581500Y19050D03* 207 | M02* 208 | -------------------------------------------------------------------------------- /.kitspace/solderpaste_bottom.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSolderpaste Bottom*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | 12 | 13 | M02* 14 | -------------------------------------------------------------------------------- /.kitspace/solderpaste_top.gbr: -------------------------------------------------------------------------------- 1 | G04 EAGLE Gerber RS-274X export* 2 | G75* 3 | %MOMM*% 4 | %FSLAX34Y34*% 5 | %LPD*% 6 | %INSolderpaste Top*% 7 | %IPPOS*% 8 | %AMOC8* 9 | 5,1,8,0,0,1.08239X$1,22.5*% 10 | G01* 11 | %ADD10R,1.270000X2.540000*% 12 | %ADD11R,1.600000X1.300000*% 13 | %ADD12R,1.300000X1.600000*% 14 | 15 | 16 | D10* 17 | X25400Y444500D03* 18 | X292100Y444500D03* 19 | X317500Y444500D03* 20 | X342900Y444500D03* 21 | X50800Y444500D03* 22 | X76200Y444500D03* 23 | X114300Y444500D03* 24 | X139700Y444500D03* 25 | X165100Y444500D03* 26 | X203200Y444500D03* 27 | X228600Y444500D03* 28 | X254000Y444500D03* 29 | D11* 30 | X628650Y276700D03* 31 | X628650Y256700D03* 32 | X628650Y104300D03* 33 | X628650Y124300D03* 34 | X571500Y537050D03* 35 | X571500Y517050D03* 36 | D12* 37 | X561500Y95250D03* 38 | X581500Y95250D03* 39 | X561500Y69850D03* 40 | X581500Y69850D03* 41 | X561500Y44450D03* 42 | X581500Y44450D03* 43 | X561500Y19050D03* 44 | X581500Y19050D03* 45 | M02* 46 | -------------------------------------------------------------------------------- /COMPILE.md: -------------------------------------------------------------------------------- 1 | ## Compile 2 | ### CLI (Requires python and python-pip) 3 | ``` 4 | git clone https://github.com/Ryzee119/usb64.git --recursive 5 | python -m pip install --upgrade pip 6 | pip install platformio 7 | cd usb64 8 | platformio run -e teensy41 9 | ``` 10 | ### Visual Studio Code 11 | * Download and install [Visual Studio Code](https://code.visualstudio.com/). 12 | * Install the [PlatformIO IDE](https://platformio.org/platformio-ide) plugin. 13 | * Clone this repo recursively `git clone https://github.com/Ryzee119/usb64.git --recursive` 14 | * In Visual Studio Code `File > Open Folder... > usb64` 15 | * Hit build on the Platform IO toolbar (`✓`). 16 | 17 | ## Program 18 | ### Teensy (using Teensy Loader) 19 | * Connect the Teensy to your PC using a MicroUSB cable. 20 | * Run the [Teensy Loader Application](https://www.pjrc.com/teensy/loader.html). 21 | 22 | ### Teensy (using Visual Studio Code) 23 | * Setup Visual Studio Code as per the Compile instructions. 24 | * Hit the program button on the Platform IO toolbar (`→`). -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Hardware Setup 2 | * Install up to two PSRAM chips on the designated footprint. Note one is optional, two is recommended. 3 | * Install a 5 x 0.1" pin header on the USB OTG Header. Note if using the PCB breakout board, the header should go on the underside. 4 | 5 | setup 6 | 7 | # Quick Start 8 | * Program the usb64 as per [compilation instructions](./COMPILE.md). 9 | * Connect a usb64 compatible USB controller. 10 | * Connect the player data lines, atleast one ground line and one 3.3V line to/from the N64 Console 11 | * Power on the Nintendo 64 console. 12 | * Power on the usb64 (5V via MicroUSB cable). 13 | 14 | basic 15 | 16 | # Breakout PCB 17 | * For a clean finish you can use this breakout PCB to house the Teensy which provide nice labelled solder pads and an embedded USB connector. 18 | * See [Hardware](./hw/README.md) for info. 19 | * Can be ordered [here](https://kitspace.org/boards/github.com/ryzee119/usb64/). 20 | 21 | # Advanced Usages 22 | * Use a USB hub to connect up to four usb64 compatible controllers. An externally powered hub may be required. 23 | * Hardwire a custom N64 controller to the designated IO (Each digital pin is internally pulled-up, Analog input respect to VCC, VCC/2 is central position). 24 | * Hook up a Raspberry Pi or similar via I2C and send button presses via I2C. 25 | 26 | advanced 27 | 28 | ## License and Attribution 29 | usb64 is shared under the [MIT license](https://github.com/Ryzee119/usb64/blob/dev/LICENSE), however this project includes code by others. Refer to the list below. 30 | * [N64 Console artwork](https://icon-library.net/icon/nintendo-64-icon-23.html) shared under [CC0 Public Domain Licence](https://creativecommons.org/publicdomain/zero/1.0/). 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ryan Wendland 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | basic 2 | 3 | ![Build](https://github.com/Ryzee119/usb64/workflows/Build/badge.svg) ![badge](https://img.shields.io/badge/license-MIT-green) 4 | A project developed to use USB controllers on the Nintendo 64 console. 5 | Precompiled binaries can be downloaded from [Releases](https://github.com/Ryzee119/usb64/releases). 6 | **NOTE: This project is still in development, PRs and ideas are welcome. See todo list for ideas.** 7 | 8 | - [x] N64 controller emulation (up to four controllers at once!). 9 | - [x] Rumblepak emulation. 10 | - [x] Mempak emulation with four selectable banks. Technically unlimited. 11 | - [x] Transferpak emulation. Put Gameboy ROMS on an SD Card! 12 | - [x] N64 mouse emulation. Use a USB Mouse! 13 | - [x] N64 Randnet emulation. Use a USB keyboard! 14 | - [x] Configurable deadzones and sensitivity from the N64 Console. 15 | - [x] True dual analog sticks with GoldenEye and Perfect Dark. 16 | - [x] SD card driver with FATFS support for storage/backup of Gameboy ROMS, mempaks etc. 17 | - [x] A single hardwired controller interface for ultimate hacking. 18 | - [x] Optional TFT LCD Support. 19 | 20 | ## Todo 21 | - [ ] Raspberry Pi interface or equivalent for all other USB controllers. 22 | - [ ] More inbuilt USB controller drivers. (PS3, Aftermarket Xbox One, OG Xbox, improved Generic HID). 23 | - [ ] USB Bluetooth stack. 24 | - [ ] Mouse/Keyboard input for controllers. 25 | - [ ] Gamecube controller support. 26 | - [ ] Better debug output (Flashing LED, SD card logging) 27 | 28 | ## Supported Controllers 29 | - Bluetooth 8bitdo/compatible controllers via the [8BitDo Wireless USB Adapter](https://www.8bitdo.com/wireless-usb-adapter/) 30 | - Wired 8bitdo controllers when they are started in X-input mode. 31 | - Xbox S/X Wired 32 | - Xbox one Wired (Genuine / PDP) 33 | - Xbox 360 Wired 34 | - Xbox 360 Wireless (Via PC USB Receiver) 35 | - PS4 Wired 36 | - A hardwired controller, use your own buttons etc. 37 | 38 | ## Controls 39 | - Back + D-Pad = Insert Mempak banks 1 to 4 40 | - Back + LB = Insert Rumblepak 41 | - Back + RB = Insert Transferpak 42 | - Back + Start = Select *virtual pak* (Use in-game mempak managers to configure the device) 43 | - Back + B = Switch to true dual-analog stick more for GoldenEye 007/Perfect Dark 44 | - Back + A = Backup buffered memory to SD Card **(DO THIS BEFORE POWER OFF!)** 45 | 46 | ## Needed Parts 47 | | Qty | Part Description | Link | 48 | |--|--|--| 49 | | 1 | Teensy 4.1 | https://www.pjrc.com/store/teensy41.html | 50 | | 1 | USB Host Cable | https://www.pjrc.com/store/cable_usb_host_t36.html | 51 | | 3 | 0.1" Pin Header | https://www.pjrc.com/store/header_24x1.html | 52 | | 2 | 64Mbit PSRAM SOIC-8 | https://www.pjrc.com/store/psram.html | 53 | | 4 | N64 Controller Extensions | [AliExpress](https://www.aliexpress.com/wholesale?catId=0&SearchText=n64%20controller%20extension) | 54 | | 1 | (Optional) ILI9341 TFT LCD Display 2.2" (320x240) | [AliExpress](https://www.aliexpress.com/wholesale?catId=0&SearchText=ili9341%20tft) | 55 | | 1 | MicroSD Card | - | 56 | | 1 | (Optional) Case | https://a360.co/2HPz0U0 | 57 | | 1 | (Optional) PCB breakout board | [Kitspace](https://kitspace.org/boards/github.com/ryzee119/usb64/) | 58 | 59 | Note: PSRAM model numbers are IPS6404L-SQ-SPN or ESP-PSRAM64H. 60 | 61 | ## Compile and Program 62 | See [COMPILE.md](./COMPILE.md). 63 | 64 | ## Install 65 | See [INSTALL.md](./INSTALL.md). 66 | 67 | ## How to Use 68 | See [USAGE.md](./USAGE.md). 69 | 70 | ## License and Attribution 71 | usb64 is shared under the [MIT license](https://github.com/Ryzee119/usb64/blob/dev/LICENSE), however this project includes code by others. Refer to the list below. 72 | * [mpaland](https://github.com/mpaland)/**[printf](https://github.com/mpaland/printf)** shared under the [MIT License](https://github.com/mpaland/printf/blob/master/LICENSE). 73 | * [USBHost_t36 fork](https://github.com/Ryzee119/USBHost_t36) shared under an '[MIT or MIT-like license](https://forum.pjrc.com/threads/29382-open-source-license-issues-when-using-teensy-products?p=79667&viewfull=1#post79667)'. 74 | * [Teensy cores](https://github.com/PaulStoffregen/cores) shared under an '[MIT or MIT-like license](https://forum.pjrc.com/threads/29382-open-source-license-issues-when-using-teensy-products?p=79667&viewfull=1#post79667)'. 75 | * [MBC emulation code](src/n64/n64_transferpak_gbcarts.c) is adapted from [Peanut-GB](https://github.com/deltabeard/Peanut-GB) shared under an MIT License. 76 | 77 | usb64 with optional case and TFT display. 78 |

vp_cont

79 | 80 | If you like my work please consider a small donation
81 | [![paypal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=49HV7N8QH9KQ8¤cy_code=AUD&source=url)
82 | -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | * [Mempaks](#mempaks) 3 | * [Rumblepaks](#rumblepaks) 4 | * [Transferpaks](#transferpaks) 5 | * [Virtualpak](#virtualpak) 6 | * [Dual Stick Mode](#dual-stick-mode) 7 | * [N64 Mouse](#n64-mouse) 8 | * [TFT LCD Display](#tft-lcd-display) 9 | * [Debug](#debug) 10 | 11 | ## Mempaks 12 | * usb64 can simulate four Mempaks simultaneously. To select a Mempak press `BACK+D-PAD` direction to select the respective bank. 13 | * Two controllers cannot have the same bank selected. The second controller will revert to a Rumblepak. 14 | * Do not unplug the usb64's power before turning off the n64 console to prevent data loss. The usb64 senses the n64 console turning off and flushes data to the SD Card. 15 | * Inserting the SD card into your PC will show Mempaks as `MEMPAKXX.MPK` where XX is the bank number. You can back these up to your PC. 16 | 17 | ## Rumblepaks 18 | * usb64 can simulate four Rumblepaks simultaneously. Rumblepaks are the default peripheral on power up. To select a Rumblepak press `BACK+LB`. 19 | * The usb controller must support force feedback. 20 | 21 | ## Transferpaks 22 | * usb64 can simulate four transferpaks simulateneously. The select a Transferpak press `BACK+RB`. The transferpak will attempt to load the previously set Gameboy or Gameboy Colour ROM from the SD Card. 23 | * To select the ROM to load, you must first use the [*VirtualPak*](#virtualpak). If a ROM isn't selected, or fails to load, it will revert to a Rumblepak. 24 | * Avoid having two controllers access the same ROM at once. 25 | * Do not unplug the usb64's power before turning off the n64 console to prevent data loss. The usb64 senses the n64 console turning off and flushes data to the SD Card. 26 | * Gameboy saves can be copied over to the SD Card for use with the Transferpak. The file name must match the ROM save with a `.SAV` extension. 27 | * You can simulate four transferpaks, with four difference ROMS, with four different save files!

tpak_6 tpak_7

silver tpak_1

tpak_5 tpak_8

28 | 29 | ## Virtualpak 30 | * The Virtualpak is one of my favourite features. It's like a Mempak, but is not used for save files. It exploits the Mempak managers built into some N64 games to configure the usb64 device! 31 | * To select the Virtualpak press `BACK+START`. 32 | * To use the Virtualpak, boot into a game that has a Mempak manager. Some games will work better than others. `Army Men: Air Combat` is a good one. `Perfect Dark` works well too. Hold START whilst the game is booting to access the Mempak manager. The follow screens show `Army Men: Air Combat` and `Perfect Dark` as an example.

vp_main vp_perfectdark

33 | * To select an item, you actually delete that note from the Mempak. usb64 detects what row you selected as if navigating a menu! 34 | * **TPAK SETTINGS** is used to configure what ROM to load into the Transferpak. This will scan the SD card for files with `.gb` and `.gbc` extensions. A `*` will print next to the currently set ROM. You can have up to ten ROMs on the SD card. After this they will just get ignored.

vp_tpak

35 | * **CONT SETTINGS** is used to configure the controller. You can change deadzone, Sensitivity, toggle on/off snapping to 45deg angles and toggle on/off a octagonal N64 stick correction. The set values is shown as a number next to the row. Each controller can be configured individually. Note: Some controllers will have deadzones or 45 degree angle snapping built in. For these, usb64 can't disable it.

vp_cont

36 | * **USB64 INFO1** shows what controller is connected to that port.

vp_info1

37 | * **USB64 INFO2** is currently a placeholder. 38 | 39 | ## Dual Stick Mode 40 | * Dual stick mode exploits a feature present in Perfect Dark and GoldenEye 007 to use two controllers at once for true dual analog stick input. 41 | * To use, the usb64 must be connected to controller port one and two as a minimum. It works by simulating two controllers with one and injecting the 2nd analog stick into port two. If a 2nd controller is connected to the usb64 when using this mode, it will get pushed to slot three. 42 | * To enable press `BACK+B`. 43 | * Set the game controller input for dual stick mode. It has been designed to work best with `Layout 2.4`.

dual_goldeneye dual_perfectdark

44 | 45 | ## N64 Mouse 46 | * usb64 can simulate four N64 Mouse peripherals simulateneously! 47 | * Just plug in a USB mouse and the usb64 will auto detect it and emulate a N64 Mouse. 48 | * The middle mouse button is mapped to START.

mouse_2 mouse_1

49 | 50 | ## TFT LCD Display 51 | * usb64 supports an optional TFT LCD display based on the low cost and extremely common ILI9341 display controller. 52 | * The display will automatically work once connected. 53 | * To cycle through different screens press `L+R`. Currently the default screen shows an overview of the current controller status. The other screen shows some useful debug info.

tft_1 tft_2

54 | 55 | ## Debug 56 | * There's alot going, and currently it may not be clear what the usb64 is doing. Until something better is implemented, you can connect the usb64 to your PC via a MicroUSB cable. This will enumerate as a serial comport. Connect to it with your favourite terminal to get some feedback. The code can be recompiled with [additional debug flags](./src/usb64_conf.h).

debug

57 | -------------------------------------------------------------------------------- /hw/README.md: -------------------------------------------------------------------------------- 1 | # Breakout PCB 2 | * This board was designed using [Autodesk Eagle](https://www.autodesk.com/products/eagle/overview). You can edit the `.brd` and `.sch` with this program. 3 | * You can order via KitSpace: https://kitspace.org/boards/github.com/ryzee119/usb64/. 4 | * Rev 1 Update: Additional 100pF capacitors have been added to each data line to improve noise rejection. See https://github.com/Ryzee119/usb64/issues/42. 5 | 6 |

schematic

7 | 8 |

pcb_breakout

9 | 10 |

pcb_render

-------------------------------------------------------------------------------- /hw/schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/hw/schematic.png -------------------------------------------------------------------------------- /images/astick.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/astick.gif -------------------------------------------------------------------------------- /images/cont_benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/cont_benchmark.png -------------------------------------------------------------------------------- /images/debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/debug.png -------------------------------------------------------------------------------- /images/dual_goldeneye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/dual_goldeneye.png -------------------------------------------------------------------------------- /images/dual_perfectdark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/dual_perfectdark.png -------------------------------------------------------------------------------- /images/install_advanced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/install_advanced.png -------------------------------------------------------------------------------- /images/install_basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/install_basic.png -------------------------------------------------------------------------------- /images/install_setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/install_setup.png -------------------------------------------------------------------------------- /images/mouse_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/mouse_1.png -------------------------------------------------------------------------------- /images/mouse_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/mouse_2.png -------------------------------------------------------------------------------- /images/pcb_breakout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/pcb_breakout.jpg -------------------------------------------------------------------------------- /images/pcb_render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/pcb_render.png -------------------------------------------------------------------------------- /images/setup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/setup.jpg -------------------------------------------------------------------------------- /images/silver.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/silver.gif -------------------------------------------------------------------------------- /images/tft_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/tft_1.png -------------------------------------------------------------------------------- /images/tft_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/tft_2.png -------------------------------------------------------------------------------- /images/tpak_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/tpak_1.png -------------------------------------------------------------------------------- /images/tpak_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/tpak_5.png -------------------------------------------------------------------------------- /images/tpak_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/tpak_6.png -------------------------------------------------------------------------------- /images/tpak_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/tpak_7.png -------------------------------------------------------------------------------- /images/tpak_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/tpak_8.png -------------------------------------------------------------------------------- /images/usb64.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/usb64.jpg -------------------------------------------------------------------------------- /images/usb64_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/usb64_logo.png -------------------------------------------------------------------------------- /images/vp_cont.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/vp_cont.png -------------------------------------------------------------------------------- /images/vp_info1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/vp_info1.png -------------------------------------------------------------------------------- /images/vp_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/vp_main.png -------------------------------------------------------------------------------- /images/vp_perfectdark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/vp_perfectdark.png -------------------------------------------------------------------------------- /images/vp_tpak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryzee119/usb64/02b63b4833374e6e246771bf3f91b3cf1b35871b/images/vp_tpak.png -------------------------------------------------------------------------------- /kitspace.yaml: -------------------------------------------------------------------------------- 1 | gerbers: .kitspace 2 | bom: .kitspace/bom.csv 3 | color: blue 4 | site: https://github.com/Ryzee119/usb64 -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; MIT License 2 | ; 3 | ; Copyright (c) [2020] [Ryan Wendland] 4 | ; 5 | ; Permission is hereby granted, free of charge, to any person obtaining a copy 6 | ; of this software and associated documentation files (the "Software"), to deal 7 | ; in the Software without restriction, including without limitation the rights 8 | ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | ; copies of the Software, and to permit persons to whom the Software is 10 | ; furnished to do so, subject to the following conditions: 11 | ; 12 | ; The above copyright notice and this permission notice shall be included in all 13 | ; copies or substantial portions of the Software. 14 | ; 15 | ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | ; SOFTWARE. 22 | ; 23 | 24 | [common_env_data] 25 | src_filter = 26 | +<*.cpp> +<*.c> 27 | + 28 | + 29 | + 30 | 31 | build_flags = 32 | -O2 33 | -Wall 34 | -Isrc/ 35 | -Isrc/n64 36 | -Isrc/printf 37 | -Isrc/tinyalloc 38 | 39 | ; Printf Configuration 40 | -DPRINTF_DISABLE_SUPPORT_FLOAT 41 | -DPRINTF_DISABLE_SUPPORT_EXPONENTIAL 42 | -DPRINTF_DISABLE_SUPPORT_LONG_LONG 43 | -DPRINTF_DISABLE_SUPPORT_PTRDIFF_T 44 | 45 | ; Tinyalloc Configuration 46 | -DTA_DISABLE_COMPACT 47 | 48 | [env:teensy41] 49 | platform = teensy@~5.0.0 50 | board = teensy41 51 | framework = arduino 52 | 53 | ; Disable the inbuilt framework lib so I can use my own fork 54 | lib_ignore = USBHost_t36 55 | 56 | build_src_filter = 57 | ${common_env_data.src_filter} 58 | + 59 | + 60 | + 61 | + 62 | 63 | build_flags = 64 | ${common_env_data.build_flags} 65 | -Isrc/USBHost_t36 66 | -Isrc/ILI9341_t3n/src 67 | -Isrc/tft 68 | ; -DUSBHOST_PRINT_DEBUG 69 | ; -DDEBUG_JOYSTICK 70 | -------------------------------------------------------------------------------- /src/analog_stick.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include "usb64_conf.h" 6 | #include "printf.h" 7 | 8 | void astick_apply_deadzone(float *out_x, float *out_y, float x, float y, float dz_low, float dz_high) { 9 | float magnitude = sqrtf(powf(x,2) + powf(y,2)); 10 | if (magnitude > dz_low) { 11 | //Scale such that output magnitude is in the range [0.0f, 1.0f] 12 | float allowed_range = 1.0f - dz_high - dz_low; 13 | float normalised_magnitude = (magnitude - dz_low) / allowed_range; 14 | if (normalised_magnitude > 1.0f) 15 | normalised_magnitude = 1.0f; 16 | float scale = normalised_magnitude / magnitude; 17 | *out_x = x * scale; 18 | *out_y = y * scale; 19 | } 20 | else { 21 | //Stick is in the inner dead zone 22 | *out_x = 0.0f; 23 | *out_y = 0.0f; 24 | } 25 | } 26 | 27 | float astick_apply_sensitivity(int sensitivity, float *x, float *y) 28 | { 29 | float range; 30 | switch (sensitivity) 31 | { 32 | case 4: range = 1.10f; break; // +/-110 33 | case 3: range = 0.95f; break; 34 | case 2: range = 0.85f; break; 35 | case 1: range = 0.75f; break; 36 | case 0: range = 0.65f; break; // +/-65 37 | default: range = 0.85f; break; 38 | } 39 | *x *= range; *y *= range; 40 | 41 | return range; 42 | } 43 | 44 | void astick_apply_snap(float range, float *x, float *y) 45 | { 46 | //+/- SNAP_RANGE degrees within a 45 degree angle will snap (MAX is 45/2) 47 | const int snap = SNAP_RANGE; 48 | float magnitude = sqrtf(powf(*x,2) + powf(*y,2)); 49 | 50 | //Only snap if magnitude is >=90% 51 | if (magnitude >= 0.90f * range) 52 | { 53 | int angle = atan2f(*y, *x) * 180.0f / 3.14f; 54 | 55 | //Between 0-360 degrees 56 | if (angle < 0) angle = 360 + angle; 57 | 58 | //Temp variable between 0-45 degrees 59 | int a = angle; 60 | while (a > 45) a-=45; 61 | 62 | //Snap to 45 degree segments 63 | if ((a <= 0 + snap) || (a >= 45 - snap)) 64 | { 65 | angle += snap; 66 | angle -= angle % 45; 67 | *x = magnitude * cosf(angle * 3.14f / 180.0f); 68 | *y = magnitude * sinf(angle * 3.14f / 180.0f); 69 | } 70 | } 71 | } 72 | 73 | //Input range is expected to a circle with radius 1.00f. 74 | //This should be normalised/deadzone corrected before entering this function. 75 | void astick_apply_octa_correction(float *x, float *y) 76 | { 77 | /* A 90 degree quadrant of the octa output. 78 | * The calculation is performed between 0-45degree section as it is mirror for each quadrant. 79 | * 80 | * #### 81 | * ##### 82 | * ##### 83 | * ## 84 | * @ # 85 | * @@ # 86 | * @@ @ <-xint,yint 87 | * m45->@@ / # 88 | * @@ / # 89 | * @@ /<-out_mag # <- octa line (m1*x + c1) 90 | * @@ / # 91 | * @@ / | angle # 92 | * 93 | * 0 1.0 94 | */ 95 | #define D2R(a) (a * 3.1415f/180.0f) 96 | static const float m45 = MAG_AT_45DEG; 97 | float angle = atanf(*y / *x); //-90 to +90deg 98 | //Make it 0 to 90deg 99 | if (angle < 0) angle += D2R(90); 100 | 101 | //Make it 0 to 45deg 102 | if (angle > D2R(45)) angle = D2R(90) - angle; 103 | 104 | //Build octagonal line for intersection 105 | float m1 = (0 - m45 * sinf(D2R(45))) / (1 - m45 * cosf(D2R(45))); 106 | float c1 = -m1; 107 | 108 | //Draw another line from the input angle 109 | float x3 = cosf(angle); 110 | float y3 = sinf(angle); 111 | float m3 = y3 / x3; 112 | 113 | //Calculate intersection between line and octagon. 114 | float xint = (0 - c1) / (m1 - m3); 115 | float yint = m1 * xint + c1; 116 | 117 | //Calculate magnitude of line until intersection. 118 | float out_mag = sqrtf(xint * xint + yint * yint); 119 | 120 | //Output corrected x,y 121 | *x *= out_mag; 122 | *y *= out_mag; 123 | } 124 | -------------------------------------------------------------------------------- /src/analog_stick.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #ifndef _ANALOG_STICK_H 5 | #define _ANALOG_STICK_H 6 | 7 | #include 8 | 9 | void astick_apply_deadzone(float *out_x, float *out_y, float x, float y, float dz_low, float dz_high); 10 | float astick_apply_sensitivity(int sensitivity, float *x, float *y); 11 | void astick_apply_snap(float range, float *x, float *y); 12 | void astick_apply_octa_correction(float *x, float *y); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/fileio.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include 6 | #include "usb64_conf.h" 7 | #include "printf.h" 8 | 9 | void fileio_init() 10 | { 11 | if (!SD.sdfs.begin(SdioConfig(FIFO_SDIO))) 12 | { 13 | debug_print_error("[FILEIO] ERROR: Could not open SD Card\n"); 14 | } 15 | else 16 | { 17 | debug_print_fatfs("[FILEIO] Opened SD card OK! Size: %lld MB\n", SD.totalSize()/1024/1024); 18 | } 19 | } 20 | 21 | /* 22 | * Function: Returns are array of strings for file the root directory up to max. 23 | * WARNING: This allocates heap memory, and must be free'd by user. 24 | * Not speed critical 25 | * ---------------------------- 26 | * Returns: Number of files found. 27 | * 28 | * array: array of char pointers of length greater than max. 29 | * max: Max number of gb roms to find. Function exits with max is reached. 30 | */ 31 | uint32_t fileio_list_directory(char **list, uint32_t max) 32 | { 33 | int file_count = 0; 34 | File root = SD.open("/"); 35 | 36 | if (root == false) 37 | { 38 | debug_print_error("[FILEIO] ERROR: Could not read SD Card\n"); 39 | return 0; 40 | } 41 | 42 | while (true) 43 | { 44 | File entry = root.openNextFile(); 45 | if (entry == false) 46 | break; 47 | 48 | if (!entry.isDirectory()) 49 | { 50 | debug_print_fatfs("Found file: %s\n", entry.name()); 51 | list[file_count] = (char *)malloc(strlen(entry.name()) + 1); 52 | strcpy(list[file_count], entry.name()); 53 | file_count++; 54 | } 55 | entry.close(); 56 | } 57 | root.close(); 58 | return file_count; 59 | } 60 | 61 | /* 62 | * Function: Backup a ram data to non-volatile storage. 63 | * ---------------------------- 64 | * Returns: Void 65 | * 66 | * filename: The filename of the saved file 67 | * data: Pointer to the array of data to be saved 68 | * len: Number of bytes to save. 69 | */ 70 | void fileio_write_to_file(char *filename, uint8_t *data, uint32_t len) 71 | { 72 | FsFile fil = SD.sdfs.open(filename, O_WRITE | O_CREAT); 73 | if (fil == false) 74 | { 75 | debug_print_error("[FILEIO] ERROR: Could not open %s for WRITE\n", filename); 76 | return; 77 | } 78 | if (fil.write(data, len) != len) 79 | { 80 | debug_print_error("[FILEIO] ERROR: Could not write %s\n", filename); 81 | } 82 | else 83 | { 84 | debug_print_status("[FILEIO] Writing %s for %u bytes ok!\n", filename, len); 85 | } 86 | fil.close(); 87 | } 88 | 89 | /* 90 | * Function: Restore a file from non-volatile storage into RAM. This will return 0x00's if the file does not exist. 91 | * Not speed critical 92 | * ---------------------------- 93 | * Returns: Void 94 | * 95 | * filename: The filename of the saved file 96 | * file_offset: Number bytes from beginning of file 97 | * data: Pointer to the array of data to be restored to 98 | * len: Number of bytes to restore. 99 | */ 100 | void fileio_read_from_file(char *filename, uint32_t file_offset, uint8_t *data, uint32_t len) 101 | { 102 | FsFile fil = SD.sdfs.open(filename, O_READ); 103 | if (fil == false) 104 | { 105 | debug_print_error("[FILEIO] ERROR: Could not open %s for READ\n", filename); 106 | return; 107 | } 108 | 109 | fil.seekSet(file_offset); 110 | 111 | if (fil.read(data, len) != (int)len) 112 | { 113 | debug_print_error("[FILEIO] ERROR: Could not read %s\n", filename); 114 | } 115 | else 116 | { 117 | debug_print_status("[FILEIO] Reading %s for %u bytes ok!\n", filename, len); 118 | } 119 | fil.close(); 120 | } 121 | -------------------------------------------------------------------------------- /src/fileio.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #ifndef _FILEIO_H 5 | #define _FILEIO_H 6 | 7 | #include 8 | #include "usb64_conf.h" 9 | 10 | void fileio_init(void); 11 | void fileio_write_to_file(char *filename, uint8_t *data, uint32_t len); 12 | void fileio_read_from_file(char *filename, uint32_t file_offset, uint8_t *data, uint32_t len); 13 | uint32_t fileio_list_directory(char **list, uint32_t max); 14 | 15 | int fileio_open_file_readonly(const char *filename); 16 | void fileio_close_file(); 17 | int fileio_get_line(char* buffer, int max_len); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #ifndef _INPUT_H 5 | #define _INPUT_H 6 | 7 | #include 8 | 9 | typedef struct 10 | { 11 | uint16_t keypad; 12 | uint16_t randnet_matrix; 13 | } randnet_map_t; 14 | 15 | static const randnet_map_t randnet_map[] = { 16 | {KEY_ESC, 0x0A08}, //Escape 17 | {KEY_F1, 0x0B01}, // F1 18 | {KEY_F2, 0x0A01}, // F2 19 | {KEY_F3, 0x0B08}, // F3 20 | {KEY_F4, 0x0A07}, // F4 21 | {KEY_F5, 0x0B07}, // F5 22 | {KEY_F6, 0x0A02}, // F6 23 | {KEY_F7, 0x0B02}, // F7 24 | {KEY_F8, 0x0A03}, // F8 25 | {KEY_F9, 0x0B03}, // F9 26 | {KEY_F10, 0x0A04}, // F10 27 | {KEY_F11, 0x0203}, // F11 28 | {KEY_F12, 0x0B06}, // F12 29 | {KEY_NUM_LOCK, 0x0A05}, // Num Lock 30 | {KEY_PRINTSCREEN, 0x0B05}, //Japanese Key below Numlock LED 31 | {KEY_SCROLL_LOCK, 0x0208}, //Japanese Key below Caps Lock LED 32 | {KEY_PAUSE, 0x0207}, // Japanese Key below Power LED 33 | {KEY_TILDE, 0x0D05}, //~ 34 | {KEY_1, 0x0C05}, //Number 1 35 | {KEY_2, 0x0505}, //Number 2 36 | {KEY_3, 0x0605}, //Number 3 37 | {KEY_4, 0x0705}, //Number 4 38 | {KEY_5, 0x0805}, //Number 5 39 | {KEY_6, 0x0905}, //Number 6 40 | {KEY_7, 0x0906}, //Number 7 41 | {KEY_8, 0x0806}, //Number 8 42 | {KEY_9, 0x0706}, //Number 9 43 | {KEY_0, 0x0606}, //Number 0 44 | {KEYPAD_MINUS, 0x0506}, //- 45 | {KEYPAD_PLUS, 0x0C06}, //^ 46 | {KEY_BACKSPACE, 0x0D06}, //Backspace 47 | {KEY_TAB, 0x0D01}, //Tab 48 | {KEY_Q, 0x0C01}, //Q 49 | {KEY_W, 0x0501}, //W 50 | {KEY_E, 0x0601}, //E 51 | {KEY_R, 0x0701}, //R 52 | {KEY_T, 0x0801}, //T 53 | {KEY_Y, 0x0901}, //Y 54 | {KEY_U, 0x0904}, //U 55 | {KEY_I, 0x0804}, //I 56 | {KEY_O, 0x0704}, //O 57 | {KEY_P, 0x0604}, //P 58 | {KEY_QUOTE, 0x0504}, //' 59 | {KEY_LEFT_BRACE, 0x0C04}, //{ 60 | {KEY_RIGHT_BRACE, 0x0406}, //} 61 | {KEY_CAPS_LOCK, 0x0F05}, //Caps Lock 62 | {KEY_A, 0x0D07}, //A 63 | {KEY_S, 0x0C07}, //S 64 | {KEY_D, 0x0507}, //D 65 | {KEY_F, 0x0607}, //F 66 | {KEY_G, 0x0707}, //G 67 | {KEY_H, 0x0807}, //H 68 | {KEY_J, 0x0907}, //J 69 | {KEY_K, 0x0903}, //K 70 | {KEY_L, 0x0803}, //L 71 | {KEYPAD_PLUS, 0x0703}, //+ 72 | {KEYPAD_ASTERIX, 0x0603}, //* 73 | {KEY_ENTER, 0x0D04}, //Enter 74 | {104, 0x0E01}, //Left Shift 75 | {KEY_Z, 0x0D08}, //Z 76 | {KEY_X, 0x0C08}, //X 77 | {KEY_C, 0x0508}, //C 78 | {KEY_V, 0x0608}, //V 79 | {KEY_B, 0x0708}, //B 80 | {KEY_N, 0x0808}, //N 81 | {KEY_M, 0x0908}, //M 82 | {KEY_COMMA, 0x0902}, //< 83 | {KEY_PERIOD, 0x0802}, //> 84 | {KEY_SLASH, 0x0702}, //? 85 | {KEY_MINUS, 0x1004}, //- (Long dash) 86 | {KEY_UP, 0x0204}, //Up Cursor 87 | {104, 0x0E06}, //Right Shift 88 | {103, 0x1107}, //Ctrl 89 | {110, 0x0F07}, //Opt 90 | {KEY_SEMICOLON, 0x1105}, //| (Pipes) 91 | {105, 0x1008}, //Alt 92 | {KEYPAD_1, 0x1002}, //Japanese 'alphanumeric key' 93 | {KEY_SPACE, 0x0602}, //Space 94 | {KEYPAD_2, 0x0E02}, //Japanese 'kana' 95 | {KEYPAD_3, 0x1006}, //Japanese Character 96 | {KEY_END, 0x0206}, //End 行末 97 | {KEY_LEFT, 0x0205}, //Left Cursor 98 | {KEY_DOWN, 0x0305}, //Down Cursor 99 | {KEY_RIGHT, 0x0405}, //Right Cursor 100 | }; 101 | 102 | enum 103 | { 104 | USB_MOUSE, 105 | USB_KB, 106 | USB_GAMECONTROLLER, 107 | HW_GAMECONTROLLER, 108 | I2C_GAMECONTROLLER 109 | }; 110 | 111 | typedef struct 112 | { 113 | void *driver; 114 | int type; 115 | } input; 116 | 117 | void input_init(); 118 | void input_update_input_devices(); 119 | bool input_is_connected(int id); 120 | bool input_is_mouse(int id); 121 | bool input_is_kb(int id); 122 | bool input_is_gamecontroller(int id); 123 | bool input_is_hw_gamecontroller(int id); 124 | uint16_t input_get_id_product(int id); 125 | uint16_t input_get_id_vendor(int id); 126 | const char *input_get_manufacturer_string(int id); 127 | const char *input_get_product_string(int id); 128 | uint16_t input_get_state(uint8_t id, void *n64_response, bool *combo_pressed); 129 | void input_apply_rumble(int id, uint8_t strength); 130 | void input_enable_dualstick_mode(int id); 131 | void input_disable_dualstick_mode(int id); 132 | bool input_is_dualstick_mode(int id); 133 | 134 | #endif 135 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include "input.h" 6 | #include "printf.h" 7 | #include "n64_wrapper.h" 8 | #include "usb64_conf.h" 9 | #include "n64_controller.h" 10 | #include "n64_transferpak_gbcarts.h" 11 | #include "n64_virtualpak.h" 12 | #include "n64_settings.h" 13 | #include "analog_stick.h" 14 | #include "memory.h" 15 | #include "fileio.h" 16 | #include "tft.h" 17 | 18 | 19 | static void ring_buffer_init(void); 20 | static void ring_buffer_flush(); 21 | 22 | n64_input_dev_t n64_in_dev[MAX_CONTROLLERS]; 23 | n64_settings *settings; 24 | int n64_is_on = 0; 25 | 26 | #if (MAX_CONTROLLERS >= 1) 27 | void n64_controller1_clock_edge() 28 | { 29 | n64_controller_hande_new_edge(&n64_in_dev[0]); 30 | } 31 | #endif 32 | #if (MAX_CONTROLLERS >= 2) 33 | void n64_controller2_clock_edge() 34 | { 35 | n64_controller_hande_new_edge(&n64_in_dev[1]); 36 | } 37 | #endif 38 | #if (MAX_CONTROLLERS >= 3) 39 | void n64_controller3_clock_edge() 40 | { 41 | n64_controller_hande_new_edge(&n64_in_dev[2]); 42 | } 43 | #endif 44 | #if (MAX_CONTROLLERS >= 4) 45 | void n64_controller4_clock_edge() 46 | { 47 | n64_controller_hande_new_edge(&n64_in_dev[3]); 48 | } 49 | #endif 50 | 51 | void setup() 52 | { 53 | //Init the serial port and ring buffer 54 | serial_port.begin(256000); 55 | 56 | //Get these up as early as possible. 57 | pinMode(N64_CONTROLLER_1_PIN, INPUT_PULLUP); 58 | pinMode(N64_CONTROLLER_2_PIN, INPUT_PULLUP); 59 | pinMode(N64_CONTROLLER_3_PIN, INPUT_PULLUP); 60 | pinMode(N64_CONTROLLER_4_PIN, INPUT_PULLUP); 61 | pinMode(N64_CONSOLE_SENSE, INPUT_PULLDOWN); 62 | 63 | ring_buffer_init(); 64 | fileio_init(); 65 | memory_init(); 66 | input_init(); 67 | tft_init(); 68 | n64_subsystem_init(n64_in_dev); 69 | 70 | //Read in settings from flash 71 | settings = (n64_settings *)memory_alloc_ram(SETTINGS_FILENAME, sizeof(n64_settings), MEMORY_READ_WRITE); 72 | n64_settings_init(settings); 73 | 74 | //Set up N64 sense pin. To determine is the N64 is turned on or off 75 | //Input is connected to the N64 3V3 line on the controller port. 76 | pinMode(N64_CONSOLE_SENSE, INPUT_PULLDOWN); 77 | 78 | pinMode(N64_FRAME, OUTPUT); 79 | 80 | pinMode(USER_LED_PIN, OUTPUT); 81 | 82 | #if (ENABLE_HARDWIRED_CONTROLLER >=1) 83 | pinMode(HW_A, INPUT_PULLUP); 84 | pinMode(HW_B, INPUT_PULLUP); 85 | pinMode(HW_CU, INPUT_PULLUP); 86 | pinMode(HW_CD, INPUT_PULLUP); 87 | pinMode(HW_CL, INPUT_PULLUP); 88 | pinMode(HW_CR, INPUT_PULLUP); 89 | pinMode(HW_DU, INPUT_PULLUP); 90 | pinMode(HW_DD, INPUT_PULLUP); 91 | pinMode(HW_DL, INPUT_PULLUP); 92 | pinMode(HW_DR, INPUT_PULLUP); 93 | pinMode(HW_START, INPUT_PULLUP); 94 | pinMode(HW_Z, INPUT_PULLUP); 95 | pinMode(HW_R, INPUT_PULLUP); 96 | pinMode(HW_L, INPUT_PULLUP); 97 | pinMode(HW_EN, INPUT_PULLUP); 98 | pinMode(HW_RUMBLE, OUTPUT); 99 | #endif 100 | 101 | #if (MAX_CONTROLLERS >= 1) 102 | n64_in_dev[0].gpio_pin = N64_CONTROLLER_1_PIN; 103 | pinMode(N64_CONTROLLER_1_PIN, INPUT_PULLUP); 104 | #endif 105 | 106 | #if (MAX_CONTROLLERS >= 2) 107 | n64_in_dev[1].gpio_pin = N64_CONTROLLER_2_PIN; 108 | pinMode(N64_CONTROLLER_2_PIN, INPUT_PULLUP); 109 | #endif 110 | 111 | #if (MAX_CONTROLLERS >= 3) 112 | n64_in_dev[2].gpio_pin = N64_CONTROLLER_3_PIN; 113 | pinMode(N64_CONTROLLER_3_PIN, INPUT_PULLUP); 114 | #endif 115 | 116 | #if (MAX_CONTROLLERS >= 4) 117 | n64_in_dev[3].gpio_pin = N64_CONTROLLER_4_PIN; 118 | pinMode(N64_CONTROLLER_4_PIN, INPUT_PULLUP); 119 | #endif 120 | NVIC_SET_PRIORITY(IRQ_GPIO6789, 1); 121 | digitalWrite(USER_LED_PIN, HIGH); 122 | } 123 | 124 | static bool n64_combo = false; 125 | void loop() 126 | { 127 | static uint8_t n64_response[MAX_CONTROLLERS][32] = {0}; 128 | 129 | ring_buffer_flush(); 130 | 131 | input_update_input_devices(); 132 | 133 | tft_try_update(); 134 | 135 | for (uint32_t c = 0; c < MAX_CONTROLLERS; c++) 136 | { 137 | if (input_is_connected(c)) 138 | { 139 | if (n64_is_on && !n64_in_dev[c].interrupt_attached) 140 | { 141 | switch (c) 142 | { 143 | case 0: attachInterrupt(digitalPinToInterrupt(n64_in_dev[c].gpio_pin), n64_controller1_clock_edge, FALLING); break; 144 | case 1: attachInterrupt(digitalPinToInterrupt(n64_in_dev[c].gpio_pin), n64_controller2_clock_edge, FALLING); break; 145 | case 2: attachInterrupt(digitalPinToInterrupt(n64_in_dev[c].gpio_pin), n64_controller3_clock_edge, FALLING); break; 146 | case 3: attachInterrupt(digitalPinToInterrupt(n64_in_dev[c].gpio_pin), n64_controller4_clock_edge, FALLING); break; 147 | } 148 | n64_in_dev[c].interrupt_attached = true; 149 | } 150 | if (input_is_gamecontroller(c)) 151 | { 152 | n64_buttonmap *new_state = (n64_buttonmap *)n64_response[c]; 153 | input_get_state(c, new_state, &n64_combo); 154 | 155 | if(n64_in_dev[c].type != N64_CONTROLLER) 156 | { 157 | n64_in_dev[c].type = N64_CONTROLLER; 158 | tft_flag_update(); 159 | } 160 | n64_settings *settings = n64_settings_get(); 161 | float x, y, range; 162 | astick_apply_deadzone(&x, &y, new_state->x_axis / 100.0f, 163 | new_state->y_axis / 100.0f, 164 | settings->deadzone[c] / 10.0f, 0.05f); 165 | 166 | if(input_is_dualstick_mode(c) && (c % 2) == 0 /*Controller 0 or 2 only*/) 167 | { 168 | //If in dual analog stick mode, force lowest sensitivity. Seems too sensitive otherwise. 169 | range = astick_apply_sensitivity(0, &x, &y); 170 | } 171 | else 172 | { 173 | range = astick_apply_sensitivity(settings->sensitivity[c], &x, &y); 174 | if (settings->snap_axis[c]) astick_apply_snap(range, &x, &y); 175 | if (settings->octa_correct[c]) astick_apply_octa_correction(&x, &y); 176 | } 177 | 178 | new_state->x_axis = x * 100.0f; 179 | new_state->y_axis = y * 100.0f; 180 | 181 | //Apply digital buttons and axis to n64 controller if combo button isnt pressed 182 | if (n64_combo == 0) 183 | { 184 | n64_in_dev[c].b_state.dButtons = new_state->dButtons; 185 | n64_in_dev[c].b_state.x_axis = new_state->x_axis; 186 | n64_in_dev[c].b_state.y_axis = new_state->y_axis; 187 | } 188 | } 189 | #if (MAX_MICE >= 1) 190 | else if (input_is_mouse(c)) 191 | { 192 | n64_buttonmap *new_state = (n64_buttonmap *)n64_response[c]; 193 | input_get_state(c, new_state, &n64_combo); 194 | 195 | if(n64_in_dev[c].type != N64_MOUSE) 196 | { 197 | n64_in_dev[c].type = N64_MOUSE; 198 | tft_flag_update(); 199 | } 200 | n64_in_dev[c].b_state.dButtons = new_state->dButtons; 201 | n64_in_dev[c].b_state.x_axis = new_state->x_axis; 202 | n64_in_dev[c].b_state.y_axis = new_state->y_axis; 203 | } 204 | #endif 205 | #if (MAX_KB >= 1) 206 | else if (input_is_kb(c)) 207 | { 208 | n64_randnet_kb *new_state = (n64_randnet_kb *)n64_response[c]; 209 | //Maintain the old led state 210 | new_state->led_state = n64_in_dev[c].kb_state.led_state; 211 | 212 | input_get_state(c, new_state, &n64_combo); 213 | 214 | if(n64_in_dev[c].type != N64_RANDNET) 215 | { 216 | n64_in_dev[c].type = N64_RANDNET; 217 | tft_flag_update(); 218 | } 219 | memcpy(&n64_in_dev[c].kb_state, new_state, sizeof(n64_randnet_kb)); 220 | } 221 | #endif 222 | } 223 | 224 | if ((!input_is_connected(c) || !n64_is_on) && n64_in_dev[c].interrupt_attached) 225 | { 226 | n64_in_dev[c].interrupt_attached = false; 227 | detachInterrupt(digitalPinToInterrupt(n64_in_dev[c].gpio_pin)); 228 | } 229 | 230 | //Get a copy of the latest n64 button presses to handle the below combos 231 | uint16_t n64_buttons = 0; 232 | if (input_is_gamecontroller(c)) 233 | { 234 | n64_buttonmap *new_state = (n64_buttonmap *)n64_response[c]; 235 | n64_buttons = new_state->dButtons; 236 | } 237 | 238 | //Apply rumble if required 239 | if (n64_in_dev[c].rpak != NULL) 240 | { 241 | if (n64_in_dev[c].rpak->state == RUMBLE_START) 242 | input_apply_rumble(c, 0xFF); 243 | if (n64_in_dev[c].rpak->state == RUMBLE_STOP) 244 | input_apply_rumble(c, 0x00); 245 | n64_in_dev[c].rpak->state = RUMBLE_APPLIED; 246 | } 247 | 248 | //Handle dual stick mode toggling 249 | static uint32_t dual_stick_toggle[MAX_CONTROLLERS] = {0}; 250 | if (n64_combo && (n64_buttons & N64_B)) 251 | { 252 | if (dual_stick_toggle[c] == 0) 253 | { 254 | input_is_dualstick_mode(c) ? input_disable_dualstick_mode(c) : input_enable_dualstick_mode(c); 255 | debug_print_status("[MAIN] Dual stick mode for %u is %u\n", c, input_is_dualstick_mode(c)); 256 | dual_stick_toggle[c] = 1; 257 | } 258 | } 259 | else 260 | { 261 | dual_stick_toggle[c] = 0; 262 | } 263 | 264 | //Handle ram flushing. Auto flushes when the N64 is turned off :) 265 | static uint32_t flushing_toggle[MAX_CONTROLLERS] = {0}; 266 | n64_is_on = digitalRead(N64_CONSOLE_SENSE); 267 | if ((n64_combo && (n64_buttons & N64_A)) || (n64_is_on == 0)) 268 | { 269 | if (flushing_toggle[c] == 0) 270 | { 271 | memory_flush_all(); 272 | debug_print_status("[MAIN] Flushed RAM to SD card as required\n"); 273 | flushing_toggle[c] = 1; 274 | tft_flag_update(); 275 | } 276 | } 277 | else 278 | { 279 | if (flushing_toggle[c]) tft_flag_update(); 280 | flushing_toggle[c] = 0; 281 | } 282 | 283 | #if (ENABLE_TFT_DISPLAY >= 1) 284 | //Cycle TFT display 285 | static uint32_t tft_toggle[MAX_CONTROLLERS] = {0}; 286 | if (n64_buttons & N64_LB && n64_buttons & N64_RB) 287 | { 288 | static uint8_t tft_page = 0; 289 | if (tft_toggle[c] == 0) 290 | { 291 | tft_page = tft_change_page(++tft_page); 292 | tft_toggle[c] = 1; 293 | tft_flag_update(); 294 | } 295 | } 296 | else 297 | { 298 | tft_toggle[c] = 0; 299 | } 300 | 301 | //Measure Teensy temp and if it has changed, flag a TFT update 302 | static int32_t teensy_temp = 0; 303 | if (abs((int32_t)tempmonGetTemp() - teensy_temp) > 2) 304 | { 305 | teensy_temp = (int32_t)tempmonGetTemp(); 306 | tft_flag_update(); 307 | } 308 | #endif 309 | 310 | //Handle peripheral change combinations 311 | static uint32_t timer_peri_change[MAX_CONTROLLERS] = {0}; 312 | if (n64_combo && (n64_buttons & N64_DU || 313 | n64_buttons & N64_DD || 314 | n64_buttons & N64_DL || 315 | n64_buttons & N64_DR || 316 | n64_buttons & N64_ST || 317 | n64_buttons & N64_LB || 318 | n64_buttons & N64_RB)) 319 | { 320 | if (n64_in_dev[c].current_peripheral == PERI_NONE) 321 | break; //Already changing peripheral 322 | 323 | timer_peri_change[c] = millis(); 324 | 325 | /* CLEAR CURRENT PERIPHERALS */ 326 | if (n64_in_dev[c].mempack != NULL) 327 | { 328 | n64_in_dev[c].mempack->data = NULL; 329 | n64_in_dev[c].mempack->id = VIRTUAL_PAK; 330 | } 331 | 332 | if (n64_in_dev[c].tpak != NULL) 333 | { 334 | tpak_reset(n64_in_dev[c].tpak); 335 | if (n64_in_dev[c].tpak->gbcart != NULL) 336 | { 337 | memory_free_item(n64_in_dev[c].tpak->gbcart->rom); 338 | n64_in_dev[c].tpak->gbcart->filename[0] = '\0'; 339 | n64_in_dev[c].tpak->gbcart->romsize = 0; 340 | n64_in_dev[c].tpak->gbcart->ramsize = 0; 341 | n64_in_dev[c].tpak->gbcart->ram = NULL; //RAM not free'd intentionally 342 | n64_in_dev[c].tpak->gbcart->rom = NULL; 343 | } 344 | } 345 | 346 | if (n64_in_dev[c].rpak != NULL) 347 | { 348 | if (n64_in_dev[c].rpak->state != RUMBLE_APPLIED) 349 | { 350 | input_apply_rumble(c, 0x00); 351 | n64_in_dev[c].rpak->state = RUMBLE_APPLIED; 352 | } 353 | } 354 | 355 | /* HANDLE NEXT PERIPHERAL */ 356 | n64_in_dev[c].current_peripheral = PERI_NONE; //Go to none whilst changing 357 | tft_force_update(); 358 | 359 | //Changing peripheral to RUMBLEPAK 360 | if (n64_buttons & N64_LB) 361 | { 362 | n64_in_dev[c].next_peripheral = PERI_RUMBLE; 363 | debug_print_status("[MAIN] C%u to rpak\n", c); 364 | } 365 | 366 | //Changing peripheral to TPAK 367 | if (n64_buttons & N64_RB) 368 | { 369 | n64_in_dev[c].next_peripheral = PERI_TPAK; 370 | debug_print_status("[MAIN] C%u to tpak\n", c); 371 | 372 | gameboycart *gb_cart = n64_in_dev[c].tpak->gbcart; 373 | uint8_t gb_header[0x100]; 374 | 375 | strcpy(gb_cart->filename, settings->default_tpak_rom[c]); 376 | if (gb_cart->filename[0] != '\0' && memory_get_ext_ram_size() > 0) 377 | { 378 | fileio_read_from_file(gb_cart->filename, 0x100, gb_header, sizeof(gb_header)); 379 | gb_init_cart(gb_cart, gb_header, settings->default_tpak_rom[c]); 380 | 381 | if (gb_cart->romsize > 0) 382 | { 383 | gb_cart->rom = memory_alloc_ram(n64_in_dev[c].tpak->gbcart->filename, gb_cart->romsize, MEMORY_READ_ONLY); 384 | } 385 | 386 | if (gb_cart->ramsize > 0) 387 | { 388 | //Readback savefile from Flash, replace .gb or .gbc with save file extension 389 | char save_filename[MAX_FILENAME_LEN]; 390 | strcpy(save_filename, n64_in_dev[c].tpak->gbcart->filename); 391 | strcpy(strrchr(save_filename, '.'), GAMEBOY_SAVE_EXT); 392 | /*WRITE ONLY IF CART HAS BATTERY*/ 393 | gb_cart->ram = memory_alloc_ram(save_filename, gb_cart->ramsize, gb_has_battery(gb_cart->mbc) == 0); 394 | } 395 | 396 | if (gb_cart->rom == NULL || (gb_cart->ram == NULL && gb_cart->ramsize > 0)) 397 | { 398 | n64_in_dev[c].next_peripheral = PERI_RUMBLE; //Error, just set to rumblepak 399 | debug_print_error("[MAIN] ERROR: Could not allocate rom or ram buffer for %s\n", n64_in_dev[c].tpak->gbcart->filename); 400 | n64_in_dev[c].tpak->gbcart->romsize = 0; 401 | n64_in_dev[c].tpak->gbcart->ramsize = 0; 402 | n64_in_dev[c].tpak->gbcart->ram = NULL; 403 | if (gb_cart->rom !=NULL) memory_free_item(gb_cart->rom); 404 | } 405 | } 406 | else 407 | { 408 | n64_in_dev[c].next_peripheral = PERI_RUMBLE; //Error, just set to rumblepak 409 | if (gb_cart->filename[0] == '\0') 410 | debug_print_error("[MAIN] ERROR: No default TPAK ROM set or no ROMs found\n"); 411 | else if (memory_get_ext_ram_size() == 0) 412 | debug_print_error("[MAIN] ERROR: No external RAM installed. TPAK disabled\n"); 413 | else 414 | debug_print_error("[MAIN] ERROR: Could not read %s\n", gb_cart->filename); 415 | } 416 | } 417 | 418 | //Changing peripheral to MEMPAK 419 | if ((n64_buttons & N64_DU || n64_buttons & N64_DD || 420 | n64_buttons & N64_DL || n64_buttons & N64_DR || 421 | n64_buttons & N64_ST)) 422 | { 423 | n64_in_dev[c].next_peripheral = PERI_MEMPAK; 424 | 425 | //Allocate mempack based on combo if available 426 | uint32_t mempak_bank = 0; 427 | uint16_t b = n64_buttons; 428 | (b & N64_DU) ? mempak_bank = 0 : (0); 429 | (b & N64_DR) ? mempak_bank = 1 : (0); 430 | (b & N64_DD) ? mempak_bank = 2 : (0); 431 | (b & N64_DL) ? mempak_bank = 3 : (0); 432 | (b & N64_ST) ? mempak_bank = VIRTUAL_PAK : (0); 433 | 434 | //Create the filename 435 | char filename[32]; 436 | snprintf(filename, sizeof(filename), "MEMPAK%02u%s", mempak_bank, MEMPAK_SAVE_EXT); 437 | 438 | //Scan controllers to see if mempack is in use 439 | for (uint32_t i = 0; i < MAX_CONTROLLERS; i++) 440 | { 441 | if (n64_in_dev[i].mempack->id == mempak_bank && mempak_bank != VIRTUAL_PAK) 442 | { 443 | debug_print_status("[MAIN] WARNING: mpak in use by C%u. Setting to rpak\n", i); 444 | n64_in_dev[c].next_peripheral = PERI_RUMBLE; 445 | break; 446 | } 447 | } 448 | 449 | //Mempack wasn't in use, so allocate it in ram 450 | if (n64_in_dev[c].next_peripheral != PERI_RUMBLE && mempak_bank != VIRTUAL_PAK) 451 | { 452 | n64_in_dev[c].mempack->data = memory_alloc_ram(filename, MEMPAK_SIZE, MEMORY_READ_WRITE); 453 | } 454 | 455 | if (n64_in_dev[c].mempack->data != NULL) 456 | { 457 | debug_print_status("[MAIN] C%u to mpak %u\n", c, mempak_bank); 458 | n64_in_dev[c].mempack->virtual_is_active = 0; 459 | n64_in_dev[c].mempack->id = mempak_bank; 460 | } 461 | else if (mempak_bank == VIRTUAL_PAK) 462 | { 463 | debug_print_status("[MAIN] C%u to virtual pak\n", c); 464 | n64_virtualpak_init(n64_in_dev[c].mempack); 465 | } 466 | else 467 | { 468 | debug_print_error("[MAIN] ERROR: Could not alloc RAM for %s, setting to rpak\n", filename); 469 | n64_in_dev[c].next_peripheral = PERI_RUMBLE; 470 | } 471 | } 472 | } 473 | 474 | //Simulate a peripheral change time. The peripheral goes to NONE 475 | //for a short period. Some games need this. 476 | if (n64_in_dev[c].current_peripheral == PERI_NONE && (millis() - timer_peri_change[c]) > PERI_CHANGE_TIME) 477 | { 478 | n64_in_dev[c].current_peripheral = n64_in_dev[c].next_peripheral; 479 | tft_flag_update(); 480 | } 481 | 482 | //Update the virtual pak if required 483 | if (n64_in_dev[c].mempack->virtual_update_req == 1) 484 | { 485 | //For the USB64-INFO1 Page, I write the controller info (PID,VID etc) 486 | char msg[256]; 487 | n64_virtualpak_update(n64_in_dev[c].mempack); //Update so we get the right page 488 | uint8_t c_page = n64_virtualpak_get_controller_page(); 489 | sprintf(msg, "%u:0x%04x/0x%04x\n%.15s\n%.15s\n", 490 | c_page + 1, 491 | input_get_id_vendor(c_page), 492 | input_get_id_product(c_page), 493 | input_get_manufacturer_string(c_page), 494 | input_get_product_string(c_page)); 495 | n64_virtualpak_write_info_1(msg); 496 | 497 | //Normal update 498 | n64_virtualpak_update(n64_in_dev[c].mempack); 499 | } 500 | 501 | } //END FOR LOOP 502 | } // MAIN LOOP 503 | 504 | /* PRINTF HANDLING */ 505 | static uint32_t ring_buffer_pos = 0; 506 | static char ring_buffer[4096]; 507 | void _putchar(char character) 508 | { 509 | ring_buffer[ring_buffer_pos] = character; 510 | tft_add_log(character); 511 | ring_buffer_pos = (ring_buffer_pos + 1) % sizeof(ring_buffer); 512 | } 513 | 514 | static void ring_buffer_init() 515 | { 516 | memset(ring_buffer, 0xFF, sizeof(ring_buffer)); 517 | } 518 | 519 | static void ring_buffer_flush() 520 | { 521 | static uint32_t _print_cursor = 0; 522 | while (ring_buffer[_print_cursor] != 0xFF) 523 | { 524 | serial_port.write(ring_buffer[_print_cursor]); 525 | ring_buffer[_print_cursor] = 0xFF; 526 | _print_cursor = (_print_cursor + 1) % sizeof(ring_buffer); 527 | } 528 | } 529 | -------------------------------------------------------------------------------- /src/memory.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | /* usb64 memory is buffered into RAM to prevent SD card latency causing issues. 5 | * - Smaller ram blocks are allocated to internal RAM. If internal alloc failes, external RAM is used 6 | * - Larger RAM blocks, (Like gameboy ROMS) are directly allocted into external RAM. 7 | * - Memory blocks can be marked as read only, so they will never write back to the SD card 8 | * - Memory blocks that are read/write and marked dirty when memory is written to them, so tehy wont write back to SD card 9 | * if they havent been touched. 10 | * Downside of this is that if you dont flush back to SD card, you may lose save data. usb64 auto senses when the n64 is powered off 11 | * and automatically flushes for you atleast. You can also manual flush with a button combo. 12 | */ 13 | 14 | #include 15 | #include "memory.h" 16 | #include "usb64_conf.h" 17 | #include "fileio.h" 18 | #include "printf.h" 19 | 20 | extern uint8_t external_psram_size; //in MB. Set in startup.c 21 | EXTMEM uint8_t ext_ram[1]; //Just to get the start of EXTMEM 22 | static uint32_t internal_size = 32768; //Smaller than this will malloc to internal RAM instead 23 | static sram_storage sram[32] = {0}; 24 | 25 | void memory_init() 26 | { 27 | if (external_psram_size == 0) 28 | return; 29 | 30 | debug_print_memory("[MEMORY] External memory initialised\n"); 31 | debug_print_memory("[MEMORY] Detected %uMB\n", external_psram_size); 32 | debug_print_memory("[MEMORY] Heap start: %08x\n", (uint32_t)ext_ram); 33 | debug_print_memory("[MEMORY] Heap end: %08x\n", (uint32_t)ext_ram + external_psram_size * 1024 * 1024); 34 | } 35 | 36 | //This function allocates and manages SRAM for mempak and gameboy roms (tpak) for the system. 37 | //SRAM is malloced into slots. Each slot stores a pointer to the memory location, its size, and 38 | //a string name to identify what that slot is used for. 39 | uint8_t *memory_alloc_ram(const char *name, uint32_t alloc_len, uint32_t read_only) 40 | { 41 | if (alloc_len == 0) 42 | { 43 | debug_print_memory("[MEMORY] WARNING: Passed 0 len\n"); 44 | return NULL; 45 | } 46 | 47 | //Loop through to see if alloced memory already exists 48 | for (unsigned int i = 0; i < sizeof(sram) / sizeof(sram[0]); i++) 49 | { 50 | if (strcmp(sram[i].name, name) == 0) 51 | { 52 | //Already malloced, check len is ok 53 | if (sram[i].len <= alloc_len) 54 | { 55 | debug_print_memory("[MEMORY] Memory already malloced for %s at 0x%08x, returning pointer to it\n", name, sram[i].data); 56 | return sram[i].data; 57 | } 58 | 59 | debug_print_error("[MEMORY] ERROR: SRAM malloced memory isnt right, resetting memory\n"); 60 | //Allocated length isnt long enough. Reset it to be memory safe 61 | if(sram[i].data != NULL) 62 | { 63 | if (sram[i].data >= ext_ram) 64 | extmem_free(sram[i].data); 65 | else 66 | free(sram[i].data); 67 | } 68 | sram[i].data = NULL; 69 | sram[i].len = 0; 70 | } 71 | } 72 | //If nothing exists, loop again to find a spot and allocate 73 | for (unsigned int i = 0; i < sizeof(sram) / sizeof(sram[0]); i++) 74 | { 75 | if (sram[i].len == 0) 76 | { 77 | //Smaller blocks are RAM are mallocs internally for better performance. Teensy has a reasonable 78 | //amount of internal RAM :) 79 | (alloc_len <= internal_size || external_psram_size == 0) ? (sram[i].data = (uint8_t *)malloc(alloc_len)) : 80 | (sram[i].data = (uint8_t *)extmem_malloc(alloc_len)); 81 | 82 | //If failed to malloc to internal RAM, try external RAM 83 | if (sram[i].data == NULL && alloc_len <= internal_size && external_psram_size > 0) 84 | sram[i].data = (uint8_t *)extmem_malloc(alloc_len); 85 | 86 | //If it still failed, no RAM left? 87 | if (sram[i].data == NULL) 88 | break; 89 | 90 | sram[i].len = alloc_len; 91 | strcpy(sram[i].name, name); 92 | 93 | fileio_read_from_file(sram[i].name, 0, 94 | sram[i].data, 95 | sram[i].len); 96 | 97 | sram[i].read_only = read_only; 98 | sram[i].dirty = 0; 99 | 100 | debug_print_memory("[MEMORY] Alloc'd %s, %u bytes at 0x%08x\n", sram[i].name, sram[i].len, sram[i].data); 101 | return sram[i].data; 102 | } 103 | } 104 | debug_print_error("[MEMORY] ERROR: No SRAM space or slots left. Flush RAM to Flash!\n"); 105 | return NULL; 106 | } 107 | 108 | void memory_free_item(void *ptr) 109 | { 110 | if (ptr == NULL) 111 | return; //Already free'd 112 | 113 | //Loop through to see if alloced memory already exists 114 | for (uint32_t i = 0; i < sizeof(sram) / sizeof(sram[0]); i++) 115 | { 116 | if (sram[i].data == ptr) 117 | { 118 | debug_print_memory("[MEMORY] Freeing %s at 0x%08x\n", sram[i].name, sram[i].data); 119 | if (sram[i].data >= ext_ram) 120 | extmem_free(sram[i].data); 121 | else 122 | free(sram[i].data); 123 | sram[i].name[0] = '\0'; 124 | sram[i].data = NULL; 125 | sram[i].len = 0; 126 | sram[i].dirty = 0; 127 | return; 128 | } 129 | } 130 | debug_print_memory("[MEMORY] WARNING: Did not free 0x%08x\n", ptr); 131 | } 132 | 133 | //Flush SRAM to flash memory if required 134 | void memory_flush_all() 135 | { 136 | noInterrupts(); 137 | for (unsigned int i = 0; i < sizeof(sram) / sizeof(sram[0]); i++) 138 | { 139 | if (sram[i].len == 0 || sram[i].data == NULL || sram[i].read_only != 0 || sram[i].dirty == 0) 140 | continue; 141 | 142 | debug_print_status("[MEMORY] Writing %s with %u bytes\n", sram[i].name, sram[i].len); 143 | fileio_write_to_file(sram[i].name, sram[i].data, sram[i].len); 144 | sram[i].dirty = 0; 145 | } 146 | interrupts(); 147 | } 148 | 149 | void memory_mark_dirty(void *ptr) 150 | { 151 | if (ptr == NULL) 152 | return; 153 | 154 | for (uint32_t i = 0; i < sizeof(sram) / sizeof(sram[0]); i++) 155 | { 156 | if (sram[i].data == ptr) 157 | { 158 | debug_print_memory("[MEMORY] Marking %s as dirty\n", sram[i].name); 159 | sram[i].dirty = 1; 160 | return; 161 | } 162 | } 163 | debug_print_error("[MEMORY] ERROR: Could not find 0x%08x\n", ptr); 164 | } 165 | 166 | uint8_t memory_get_ext_ram_size() 167 | { 168 | return external_psram_size; 169 | } -------------------------------------------------------------------------------- /src/memory.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #ifndef _MEMORY_H 5 | #define _MEMORY_H 6 | 7 | #include 8 | #include "usb64_conf.h" 9 | 10 | #define MEMORY_READ_WRITE 0 11 | #define MEMORY_READ_ONLY 1 12 | 13 | typedef struct 14 | { 15 | char name[MAX_FILENAME_LEN]; 16 | uint8_t *data; 17 | uint32_t len; 18 | uint32_t read_only; //If read only, it will never write back to storage 19 | uint32_t dirty; 20 | } sram_storage; 21 | 22 | void memory_init(); 23 | uint8_t *memory_alloc_ram(const char *name, uint32_t alloc_len, uint32_t read_only); 24 | void memory_flush_all(void); 25 | void memory_free_item(void *ptr); 26 | void memory_mark_dirty(void *ptr); 27 | uint8_t memory_get_ext_ram_size(); 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/n64/n64_controller.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include "printf.h" 6 | #include "usb64_conf.h" 7 | #include "n64_mempak.h" 8 | #include "n64_virtualpak.h" 9 | #include "n64_rumblepak.h" 10 | #include "n64_settings.h" 11 | #include "n64_transferpak_gbcarts.h" 12 | #include "n64_controller.h" 13 | #include "n64_wrapper.h" 14 | 15 | //Uncomment to enable mempak READ address CRC checks. 16 | //May cause timing issues. 17 | //Address CRC check for WRITES are hardcoded on already. 18 | //#define USE_N64_ADDRESS_CRC 19 | 20 | n64_rumblepak n64_rpak[MAX_CONTROLLERS]; 21 | n64_mempack n64_mpack[MAX_CONTROLLERS]; 22 | n64_transferpak n64_tpak[MAX_CONTROLLERS]; 23 | gameboycart gb_cart[MAX_CONTROLLERS]; 24 | 25 | //Hardcoded controller responses for identify requests 26 | static uint8_t n64_mouse[] = {0x02, 0x00, 0x00}; 27 | static uint8_t n64_randnet[] = {0x00, 0x02, 0x00}; 28 | static uint8_t n64_cont_with_peri[] = {0x05, 0x00, 0x01}; 29 | static uint8_t n64_cont_no_peri[] = {0x05, 0x00, 0x02}; 30 | static uint8_t n64_cont_crc_error[] = {0x05, 0x00, 0x04}; 31 | 32 | void n64_subsystem_init(n64_input_dev_t *in_dev) 33 | { 34 | // INITIALISE THE N64 STRUCTS // 35 | for (uint32_t i = 0; i < MAX_CONTROLLERS; i++) 36 | { 37 | in_dev[i].id = i; 38 | in_dev[i].current_bit = 7; 39 | in_dev[i].current_byte = 0; 40 | in_dev[i].current_peripheral = PERI_RUMBLE; 41 | in_dev[i].next_peripheral = in_dev[i].current_peripheral; 42 | in_dev[i].rpak = &n64_rpak[i]; 43 | in_dev[i].mempack = &n64_mpack[i]; 44 | in_dev[i].mempack->id = VIRTUAL_PAK; 45 | in_dev[i].mempack->data = NULL; 46 | in_dev[i].tpak = &n64_tpak[i]; 47 | in_dev[i].tpak->gbcart = &gb_cart[i]; 48 | in_dev[i].interrupt_attached = false; 49 | in_dev[i].peri_access = 0; 50 | in_dev[i].type = N64_CONTROLLER; 51 | } 52 | 53 | //Setup the Controller pin IO mapping and interrupts 54 | n64hal_hs_tick_init(); 55 | } 56 | 57 | static uint8_t n64_get_crc(uint8_t *data) 58 | { 59 | //Generated from http://www.sunshine2k.de/coding/javascript/crc/crc_js.html 60 | //N64 CRC poly was brute forced as x^7 + x^2 + x^0 (0x85), initial value = 0 61 | static uint8_t crctable[256] = { 62 | 0x00, 0x85, 0x8F, 0x0A, 0x9B, 0x1E, 0x14, 0x91, 0xB3, 0x36, 0x3C, 63 | 0xB9, 0x28, 0xAD, 0xA7, 0x22, 0xE3, 0x66, 0x6C, 0xE9, 0x78, 0xFD, 64 | 0xF7, 0x72, 0x50, 0xD5, 0xDF, 0x5A, 0xCB, 0x4E, 0x44, 0xC1, 0x43, 65 | 0xC6, 0xCC, 0x49, 0xD8, 0x5D, 0x57, 0xD2, 0xF0, 0x75, 0x7F, 0xFA, 66 | 0x6B, 0xEE, 0xE4, 0x61, 0xA0, 0x25, 0x2F, 0xAA, 0x3B, 0xBE, 0xB4, 67 | 0x31, 0x13, 0x96, 0x9C, 0x19, 0x88, 0x0D, 0x07, 0x82, 0x86, 0x03, 68 | 0x09, 0x8C, 0x1D, 0x98, 0x92, 0x17, 0x35, 0xB0, 0xBA, 0x3F, 0xAE, 69 | 0x2B, 0x21, 0xA4, 0x65, 0xE0, 0xEA, 0x6F, 0xFE, 0x7B, 0x71, 0xF4, 70 | 0xD6, 0x53, 0x59, 0xDC, 0x4D, 0xC8, 0xC2, 0x47, 0xC5, 0x40, 0x4A, 71 | 0xCF, 0x5E, 0xDB, 0xD1, 0x54, 0x76, 0xF3, 0xF9, 0x7C, 0xED, 0x68, 72 | 0x62, 0xE7, 0x26, 0xA3, 0xA9, 0x2C, 0xBD, 0x38, 0x32, 0xB7, 0x95, 73 | 0x10, 0x1A, 0x9F, 0x0E, 0x8B, 0x81, 0x04, 0x89, 0x0C, 0x06, 0x83, 74 | 0x12, 0x97, 0x9D, 0x18, 0x3A, 0xBF, 0xB5, 0x30, 0xA1, 0x24, 0x2E, 75 | 0xAB, 0x6A, 0xEF, 0xE5, 0x60, 0xF1, 0x74, 0x7E, 0xFB, 0xD9, 0x5C, 76 | 0x56, 0xD3, 0x42, 0xC7, 0xCD, 0x48, 0xCA, 0x4F, 0x45, 0xC0, 0x51, 77 | 0xD4, 0xDE, 0x5B, 0x79, 0xFC, 0xF6, 0x73, 0xE2, 0x67, 0x6D, 0xE8, 78 | 0x29, 0xAC, 0xA6, 0x23, 0xB2, 0x37, 0x3D, 0xB8, 0x9A, 0x1F, 0x15, 79 | 0x90, 0x01, 0x84, 0x8E, 0x0B, 0x0F, 0x8A, 0x80, 0x05, 0x94, 0x11, 80 | 0x1B, 0x9E, 0xBC, 0x39, 0x33, 0xB6, 0x27, 0xA2, 0xA8, 0x2D, 0xEC, 81 | 0x69, 0x63, 0xE6, 0x77, 0xF2, 0xF8, 0x7D, 0x5F, 0xDA, 0xD0, 0x55, 82 | 0xC4, 0x41, 0x4B, 0xCE, 0x4C, 0xC9, 0xC3, 0x46, 0xD7, 0x52, 0x58, 83 | 0xDD, 0xFF, 0x7A, 0x70, 0xF5, 0x64, 0xE1, 0xEB, 0x6E, 0xAF, 0x2A, 84 | 0x20, 0xA5, 0x34, 0xB1, 0xBB, 0x3E, 0x1C, 0x99, 0x93, 0x16, 0x87, 85 | 0x02, 0x08, 0x8D}; 86 | uint8_t crc = 0; 87 | for (uint32_t byte = 0; byte < 32; byte++) 88 | { 89 | crc = (uint8_t)(crctable[(uint8_t)(data[byte] ^ crc)]); 90 | } 91 | return crc; //Returns the non-inverted CRC of a 32-byte data stream 92 | } 93 | 94 | //Need to pass the 16bit address WITH the address CRC bits populated. 95 | //Return 1 if the address CRC from the console matches the internal calculation. 96 | static uint8_t n64_compare_addr_crc(uint16_t encoded_add_console) 97 | { 98 | //See http://svn.navi.cx/misc/trunk/wasabi/devices/cube64/notes/addr_encoder.py 99 | static uint8_t crctable[11] = { 100 | 0x15, 0x1F, 0x0B, 0x16, 0x19, 0x07, 0x0E, 0x1C, 0x0D, 0x1A, 0x01}; 101 | 102 | uint16_t encoded_add_cont = encoded_add_console & 0xFFE0; 103 | for (uint32_t i = 0; i < sizeof(crctable); i++) 104 | { 105 | if (encoded_add_cont & (1 << (i + 5))) 106 | encoded_add_cont ^= crctable[i]; 107 | } 108 | return encoded_add_cont == encoded_add_console; 109 | } 110 | 111 | static void n64_send_stream(uint8_t *txbuff, uint32_t len, n64_input_dev_t *c) 112 | { 113 | uint32_t cycle_cnt = 0; 114 | uint32_t cycle_start = 0; 115 | uint32_t current_byte = 0; 116 | uint32_t current_bit = 8; 117 | uint32_t U_SEC = n64hal_hs_tick_get_speed() / 1000000; //clocks per microsecond 118 | 119 | cycle_start = n64hal_hs_tick_get(); 120 | while (len > 0) 121 | { 122 | while ((n64hal_hs_tick_get() - cycle_start) < cycle_cnt); 123 | n64hal_input_swap(c, N64_OUTPUT); //OUTPUT_PP will pull low 124 | (txbuff[current_byte] & 0x80) ? (cycle_cnt += 1 * U_SEC) : (cycle_cnt += 3 * U_SEC); 125 | while ((n64hal_hs_tick_get() - cycle_start) < cycle_cnt); 126 | n64hal_input_swap(c, N64_INPUT); 127 | (txbuff[current_byte] & 0x80) ? (cycle_cnt += 3 * U_SEC) : (cycle_cnt += 1 * U_SEC); 128 | txbuff[current_byte] = txbuff[current_byte] << 1; 129 | current_bit--; 130 | 131 | //next byte if current byte complete 132 | if (current_bit == 0) 133 | { 134 | current_bit = 8; 135 | current_byte++; 136 | len--; 137 | } 138 | } 139 | 140 | //Send stop bit. Pull low for 2us, then release. 141 | while ((n64hal_hs_tick_get() - cycle_start) < cycle_cnt); 142 | n64hal_input_swap(c, N64_OUTPUT); 143 | cycle_cnt += 2 * U_SEC; 144 | while ((n64hal_hs_tick_get() - cycle_start) < cycle_cnt); 145 | n64hal_input_swap(c, N64_INPUT); //Release bus. We're done 146 | } 147 | 148 | static void n64_reset_stream(n64_input_dev_t *cont) 149 | { 150 | cont->current_bit = 7; 151 | cont->current_byte = 0; 152 | cont->data_buffer[0] = 0; 153 | } 154 | 155 | static void inline n64_wait_micros(uint32_t micros){ 156 | uint32_t end_clock = n64hal_hs_tick_get() + micros * (n64hal_hs_tick_get_speed() / 1000000); 157 | while (n64hal_hs_tick_get() < end_clock); 158 | } 159 | 160 | //This function is called in the falling edge of the n64 data bus. 161 | void n64_controller_hande_new_edge(n64_input_dev_t *cont) 162 | { 163 | uint32_t start_clock = n64hal_hs_tick_get(); 164 | 165 | //If bus has been idle for 300us, start of a new stream. 166 | if ((n64hal_hs_tick_get() - cont->bus_idle_timer_clks) > (300 * n64hal_hs_tick_get_speed() / 1000000)) 167 | { 168 | n64_reset_stream(cont); 169 | cont->peri_access = 0; 170 | } 171 | 172 | //If byte has completed, increment buffer for next byte and reset bit counter. 173 | if (cont->current_bit == -1) 174 | { 175 | cont->current_bit = 7; 176 | cont->current_byte++; 177 | (cont->current_byte > N64_MAX_POS) ? cont->current_byte = 0 : (0); 178 | cont->data_buffer[cont->current_byte] = 0x00; 179 | } 180 | 181 | //Wait for ~1.05us to pass since falling edge before reading bit 182 | uint32_t sample_clock = start_clock + (n64hal_hs_tick_get_speed() / 950000); 183 | while (n64hal_hs_tick_get() < sample_clock); 184 | 185 | //Read bit 186 | cont->data_buffer[cont->current_byte] |= n64hal_input_read(cont) << cont->current_bit; 187 | cont->current_bit -= 1; 188 | 189 | //Reset idle timer 190 | cont->bus_idle_timer_clks = n64hal_hs_tick_get(); 191 | 192 | //If byte 0 has been completed, we need to identify what the command is 193 | if (cont->current_byte == N64_COMMAND_POS + 1) 194 | { 195 | switch (cont->data_buffer[N64_COMMAND_POS]) 196 | { 197 | case N64_IDENTIFY: 198 | case N64_CONTROLLER_RESET: 199 | if (cont->type == N64_MOUSE) 200 | memcpy(&cont->data_buffer[N64_DATA_POS], n64_mouse, sizeof(n64_mouse)); 201 | 202 | else if (cont->type == N64_RANDNET) 203 | memcpy(&cont->data_buffer[N64_DATA_POS], n64_randnet, sizeof(n64_randnet)); 204 | 205 | else if (cont->current_peripheral != PERI_NONE && !cont->crc_error) 206 | memcpy(&cont->data_buffer[N64_DATA_POS], n64_cont_with_peri, sizeof(n64_cont_with_peri)); 207 | 208 | else if (cont->current_peripheral != PERI_NONE && cont->crc_error) 209 | memcpy(&cont->data_buffer[N64_DATA_POS], n64_cont_crc_error, sizeof(n64_cont_crc_error)); 210 | 211 | else 212 | memcpy(&cont->data_buffer[N64_DATA_POS], n64_cont_no_peri, sizeof(n64_cont_no_peri)); 213 | 214 | cont->crc_error = 0; 215 | n64_wait_micros(2); 216 | n64_send_stream(&cont->data_buffer[N64_DATA_POS], 3, cont); 217 | n64_reset_stream(cont); 218 | break; 219 | 220 | case N64_CONTROLLER_STATUS: 221 | if (cont->type == N64_RANDNET) //Randnet does not response to this 222 | break; 223 | n64hal_output_set(N64_FRAME, 1); 224 | n64_wait_micros(2); 225 | n64_send_stream((uint8_t *)&cont->b_state, 4, cont); 226 | n64_reset_stream(cont); 227 | cont->b_state.dButtons = 0x0000; 228 | n64hal_output_set(N64_FRAME, 0); 229 | break; 230 | case N64_RANDNET_REQ: 231 | break; 232 | case N64_PERI_READ: 233 | case N64_PERI_WRITE: 234 | cont->peri_access = 1; 235 | break; 236 | default: 237 | cont->peri_access = 0; 238 | n64_reset_stream(cont); 239 | break; 240 | } 241 | } 242 | 243 | //If it's a RANDNET keyboard packet 244 | if (cont->type == N64_RANDNET && cont->data_buffer[N64_COMMAND_POS] == N64_RANDNET_REQ && cont->current_byte == RANDNET_BTN_POS) 245 | { 246 | //First received byte is the led state of the keyboard LEDs 247 | cont->kb_state.led_state = cont->data_buffer[RANDNET_LED_POS]; 248 | debug_print_n64("[N64] Randnet LED Status %02x\n", cont->kb_state.led_state); 249 | 250 | //Build the output. Buttons are byte reversed so its correct on the output 251 | cont->data_buffer[RANDNET_BTN_POS + 0] = cont->kb_state.buttons[0] >> 8; 252 | cont->data_buffer[RANDNET_BTN_POS + 1] = cont->kb_state.buttons[0] >> 0; 253 | cont->data_buffer[RANDNET_BTN_POS + 2] = cont->kb_state.buttons[1] >> 8; 254 | cont->data_buffer[RANDNET_BTN_POS + 3] = cont->kb_state.buttons[1] >> 0; 255 | cont->data_buffer[RANDNET_BTN_POS + 4] = cont->kb_state.buttons[2] >> 8; 256 | cont->data_buffer[RANDNET_BTN_POS + 5] = cont->kb_state.buttons[2] >> 0; 257 | cont->data_buffer[RANDNET_BTN_POS + 6] = cont->kb_state.flags; 258 | n64_wait_micros(2); 259 | 260 | //Response is 7 bytes. 3 x 16bit buttons + 1 x 8bit status flags 261 | n64_send_stream(&cont->data_buffer[RANDNET_BTN_POS], 7, cont); 262 | 263 | //We're done with this packet 264 | n64_reset_stream(cont); 265 | } 266 | 267 | //If we are accessing the peripheral bus, let's handle that 268 | if (cont->peri_access == 1) 269 | { 270 | uint16_t peri_address = (cont->data_buffer[N64_ADDRESS_MSB_POS] << 8 | 271 | cont->data_buffer[N64_ADDRESS_LSB_POS]); 272 | 273 | /*WRITE ACCESS*/ 274 | //If there was a 'write' command to the peripheral bus, check if all 32 bytes of data have been received 275 | if (cont->data_buffer[N64_COMMAND_POS] == N64_PERI_WRITE && cont->current_byte == (N64_DATA_POS + 32)) 276 | { 277 | if (!n64_compare_addr_crc(peri_address)) 278 | { 279 | cont->crc_error = 1; 280 | debug_print_error("[N64] ERROR: Address CRC Error %04x\n", peri_address); 281 | } 282 | 283 | peri_address &= 0xFFE0; 284 | cont->data_buffer[N64_CRC_POS] = n64_get_crc(&cont->data_buffer[N64_DATA_POS]); 285 | //If no peripheral, the CRC is inverted 286 | if (cont->current_peripheral == PERI_NONE) 287 | cont->data_buffer[N64_CRC_POS] = ~cont->data_buffer[N64_CRC_POS]; 288 | 289 | //Send the data CRC out straight away. N64 expects this very quickly 290 | n64_wait_micros(2); 291 | n64_send_stream(&cont->data_buffer[N64_CRC_POS], 1, cont); 292 | 293 | //Now handle the write command 294 | switch (peri_address >> 12) 295 | { 296 | case 0x0: 297 | //VIRTUAL MEMPAK NOTE TABLE HOOK 298 | if (cont->mempack->virtual_is_active && cont->current_peripheral == PERI_MEMPAK && 299 | peri_address >= 0x300 && peri_address < 0x500) 300 | { 301 | /* 302 | * When you 'delete' a note from the mempak manager, I can hook the 303 | * address being deleted and determine what row you selected. 304 | * The note table is located between 0x300 and 0x500 in the mempak 305 | * and has 32bytes (0x20) per note. 306 | * I use this as a hacky menu for the N64 307 | */ 308 | uint32_t row = (peri_address - 0x300) / 0x20; //What row you have 'selected' 0-15 309 | cont->mempack->virtual_update_req = 1; 310 | cont->mempack->virtual_selected_row = row; 311 | debug_print_n64("[N64] Virtualpak write at row %u\n", row); 312 | break; 313 | } 314 | //Intentional fallthrough 315 | case 0x1: 316 | case 0x2: 317 | case 0x3: 318 | case 0x4: 319 | case 0x5: 320 | case 0x6: 321 | case 0x7: 322 | if (cont->current_peripheral == PERI_MEMPAK && !cont->crc_error) 323 | n64_mempack_write32(cont->mempack, peri_address, &cont->data_buffer[N64_DATA_POS]); 324 | break; 325 | case 0x8: 326 | if (cont->current_peripheral == PERI_TPAK) 327 | { 328 | //N64 writes 32 bytes of 0x84 to turn on the TPAK 329 | (cont->data_buffer[N64_DATA_POS] == 0x84) ? cont->tpak->power_state = 1 : (0); 330 | (cont->data_buffer[N64_DATA_POS] == 0xFE) ? tpak_reset(cont->tpak) : (0); 331 | debug_print_tpak("[TPAK] Powerstate set to %u\n", cont->tpak->power_state); 332 | } 333 | else if (cont->current_peripheral == PERI_RUMBLE) 334 | { 335 | //N64 writes 32 bytes of 0x80 to initialise the rumblepak, 0xFE to reset it 336 | (cont->data_buffer[N64_DATA_POS] == 0x80) ? cont->rpak->initialised = 1 : (0); 337 | (cont->data_buffer[N64_DATA_POS] == 0xFE) ? cont->rpak->initialised = 0 : (0); 338 | debug_print_n64("[N64] Rumblepak Status %u\n", cont->rpak->initialised); 339 | } 340 | break; 341 | case 0xA: 342 | if (cont->current_peripheral == PERI_TPAK) 343 | { 344 | //0x00, 0x01, or 0x02 and switches over the MBC memory space. 345 | cont->tpak->selected_mbc_bank = cont->data_buffer[N64_DATA_POS]; 346 | debug_print_tpak("[TPAK] MBC bank changed to %u\n", cont->tpak->selected_mbc_bank); 347 | } 348 | break; 349 | case 0xB: 350 | if (cont->current_peripheral == PERI_TPAK) 351 | { 352 | cont->tpak->access_state_changed = cont->tpak->access_state != cont->data_buffer[N64_DATA_POS]; 353 | cont->tpak->access_state = cont->data_buffer[N64_DATA_POS]; 354 | debug_print_tpak("[TPAK] Access state set to %u\n", cont->tpak->access_state); 355 | } 356 | break; 357 | case 0xC: 358 | if (cont->current_peripheral == PERI_RUMBLE) 359 | { 360 | (cont->data_buffer[N64_DATA_POS] == 0x01) ? (cont->rpak->state = RUMBLE_START) : 361 | (cont->rpak->state = RUMBLE_STOP); 362 | break; 363 | } 364 | //Intentional fallthrough 365 | case 0xD: 366 | case 0xE: 367 | case 0xF: 368 | if (cont->current_peripheral == PERI_TPAK) 369 | { 370 | tpak_write(cont->tpak, peri_address, &cont->data_buffer[N64_DATA_POS]); 371 | } 372 | break; 373 | } 374 | 375 | cont->peri_access = 0; 376 | n64_reset_stream(cont); 377 | } 378 | 379 | /* READ ACCESS 380 | * If we are reading from mempak, check if the address bytes have been 381 | * received (i.e cont->current_byte and bit is pass the address bits) 382 | * 383 | * Reads from address 0x8000 to 0x9FFF has specific responses based on the peripheral 384 | * installed. This is handled in the code below 385 | */ 386 | else if (cont->data_buffer[N64_COMMAND_POS] == N64_PERI_READ 387 | #ifdef USE_N64_ADDRESS_CRC 388 | && cont->current_byte == N64_DATA_POS /* N64_ADDRESS_LSB_POS */ 389 | #else 390 | && cont->current_byte == N64_ADDRESS_LSB_POS && cont->current_bit == 4 391 | #endif 392 | ) 393 | { 394 | memset(&cont->data_buffer[N64_DATA_POS], 0x00, 32); //N64 responds with 0x00s unless otherwise set 395 | 396 | #ifdef USE_N64_ADDRESS_CRC 397 | if (!n64_compare_addr_crc(peri_address)) 398 | { 399 | cont->crc_error = 1; 400 | debug_print_error("[N64] ERROR: Address CRC Error %04x\n", peri_address); 401 | } 402 | #endif 403 | 404 | //Clear the address CRC bits 405 | peri_address &= 0xFFE0; 406 | switch(peri_address >> 12) 407 | { 408 | case 0x0: //0x0000 - 0x0FFFF 409 | case 0x1: //0x1000 = 0x1FFFF 410 | if (cont->current_peripheral == PERI_TPAK && cont->tpak->power_state == 1) 411 | { 412 | memset(&cont->data_buffer[N64_DATA_POS], 0x84, 32); 413 | debug_print_tpak("[TPAK] Request powerstate. Sending 0x84s (its enabled)\n"); 414 | break; 415 | } 416 | //Fall through intentional 417 | case 0x3: //0x3000 - 0x3FFF 418 | case 0xB: //0xB000 - 0xBFFF 419 | if (cont->current_peripheral == PERI_TPAK) 420 | { 421 | if (cont->tpak->power_state != 1) break; 422 | if (cont->tpak->gbcart == NULL) 423 | { 424 | memset(&cont->data_buffer[N64_DATA_POS], 0x44, 32); //Return 0x44's if no cart is installed. 425 | break; 426 | } 427 | 428 | memset(&cont->data_buffer[N64_DATA_POS], (cont->tpak->access_state) ? 0x89 : 0x80, 32); 429 | debug_print_tpak("[TPAK] Request access_state. Sending 0x%02x\n", (cont->tpak->access_state) ? 0x89 : 0x80); 430 | //Set bit 2 of the first return value if the access mode was changed since last check. 431 | cont->data_buffer[N64_DATA_POS] |= (cont->tpak->access_state_changed << 2); 432 | cont->tpak->access_state_changed = 0; 433 | break; 434 | } 435 | //Fall through intentional 436 | case 0x4: 437 | case 0x5: 438 | case 0x6: 439 | case 0x7: 440 | if (cont->current_peripheral == PERI_MEMPAK) 441 | { 442 | n64_mempack_read32(cont->mempack, peri_address, &cont->data_buffer[N64_DATA_POS]); 443 | } 444 | break; 445 | case 0x8: 446 | case 0x9: 447 | //If rumblepak is initialised, respond with 32bytes of 0x80. 448 | if (cont->current_peripheral == PERI_RUMBLE && cont->rpak->initialised == 1) 449 | { 450 | memset(&cont->data_buffer[N64_DATA_POS], 0x80, 32); 451 | debug_print_n64("[N64] Request rumblepak state. Sending 0x80s (its initialised)\n"); 452 | } 453 | //If tpak is powered up, respond with 32bytes of 0x84. 454 | else if (cont->current_peripheral == PERI_TPAK && cont->tpak->power_state == 1) 455 | { 456 | memset(&cont->data_buffer[N64_DATA_POS], 0x84, 32); 457 | debug_print_tpak("[TPAK] Request tpak power state. Sending 0x84s (its powered up)\n"); 458 | } 459 | break; 460 | case 0xC: 461 | case 0xD: 462 | case 0xE: 463 | case 0xF: 464 | if(cont->current_peripheral == PERI_TPAK && cont->tpak->gbcart) 465 | { 466 | if (cont->tpak->power_state != 1) break; 467 | tpak_read(cont->tpak, peri_address, &cont->data_buffer[N64_DATA_POS]); 468 | } 469 | break; 470 | } 471 | 472 | //Calculate the CRC of the data buffer and place it at the end of the packet. 473 | cont->data_buffer[N64_CRC_POS] = n64_get_crc(&cont->data_buffer[N64_DATA_POS]); 474 | 475 | //CRC is inverted when no peripheral is installed. 476 | if (cont->current_peripheral == PERI_NONE) 477 | cont->data_buffer[N64_CRC_POS] = ~cont->data_buffer[N64_CRC_POS]; 478 | 479 | #ifdef USE_N64_ADDRESS_CRC 480 | n64_wait_micros(5); 481 | #else 482 | n64_wait_micros(30); 483 | #endif 484 | n64_send_stream(&cont->data_buffer[N64_DATA_POS], 33, cont); 485 | 486 | cont->peri_access = 0; 487 | n64_reset_stream(cont); 488 | } 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /src/n64/n64_controller.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #ifndef _N64_CONTROLLER_h 5 | #define _N64_CONTROLLER_h 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #include "n64_mempak.h" 12 | #include "n64_rumblepak.h" 13 | #include "n64_transferpak_gbcarts.h" 14 | 15 | typedef struct __attribute__((packed)) 16 | { 17 | uint16_t dButtons; 18 | int8_t x_axis; 19 | int8_t y_axis; 20 | } n64_buttonmap; 21 | 22 | typedef struct __attribute__((packed)) 23 | { 24 | uint8_t led_state; 25 | uint16_t buttons[3]; 26 | uint8_t flags; 27 | } n64_randnet_kb; 28 | 29 | typedef enum 30 | { 31 | N64_CONTROLLER, 32 | N64_MOUSE, 33 | N64_RANDNET 34 | } n64_input_type; 35 | 36 | typedef enum 37 | { 38 | PERI_NONE, 39 | PERI_RUMBLE, 40 | PERI_MEMPAK, 41 | PERI_TPAK 42 | } n64_peri_type; 43 | 44 | typedef struct 45 | { 46 | uint32_t id; //Controller ID 47 | int32_t current_bit; //The current bit to being received in 48 | uint32_t current_byte; //The current byte being received in 49 | uint8_t data_buffer[50]; //Controller main tx and rx buffer 50 | uint32_t peri_access; //Peripheral flag is set when a peripheral is being accessed 51 | uint32_t crc_error; //Set if the 2 byte address has a CRC error. 52 | n64_input_type type; //Store the type of input device. Controller, Mouse. Randnet etc. 53 | n64_buttonmap b_state; //N64 controller button and analog stick map 54 | n64_randnet_kb kb_state; //Randnet keyboard object 55 | n64_peri_type current_peripheral; //Peripheral flag, PERI_NONE, PERI_RUMBLE, PERI_MEMPAK, PERI_TPAK 56 | n64_peri_type next_peripheral; //What Peripheral to change to next after timer 57 | n64_transferpak *tpak; //Pointer to installed transferpak 58 | n64_rumblepak *rpak; //Pointer to installed rumblepak 59 | n64_mempack *mempack; //Pointer to installed mempack 60 | // 61 | uint32_t interrupt_attached; //Flag is set when this controller is connected to an ext int. 62 | uint32_t bus_idle_timer_clks; //Timer counter for bus idle timing 63 | uint32_t gpio_pin; //What pin is this controller connected to 64 | } n64_input_dev_t; 65 | 66 | //N64 JOYBUS 67 | #define N64_IDENTIFY 0x00 68 | #define N64_CONTROLLER_STATUS 0x01 69 | #define N64_PERI_READ 0x02 70 | #define N64_PERI_WRITE 0x03 71 | #define N64_CONTROLLER_RESET 0xFF 72 | #define N64_RANDNET_REQ 0x13 73 | #define N64_COMMAND_POS 0 74 | #define N64_ADDRESS_MSB_POS 1 75 | #define N64_ADDRESS_LSB_POS 2 76 | #define N64_DATA_POS 3 77 | #define N64_CRC_POS 35 78 | #define N64_MAX_POS 36 79 | 80 | //N64 Controller 81 | #define N64_A (1UL << 7) 82 | #define N64_B (1UL << 6) 83 | #define N64_Z (1UL << 5) 84 | #define N64_ST (1UL << 4) 85 | #define N64_DU (1UL << 3) 86 | #define N64_DD (1UL << 2) 87 | #define N64_DL (1UL << 1) 88 | #define N64_DR (1UL << 0) 89 | #define N64_RES (1UL << 15) 90 | #define NOTUSED (1UL << 14) 91 | #define N64_LB (1UL << 13) 92 | #define N64_RB (1UL << 12) 93 | #define N64_CU (1UL << 11) 94 | #define N64_CD (1UL << 10) 95 | #define N64_CL (1UL << 9) 96 | #define N64_CR (1UL << 8) 97 | 98 | //RANDNET 99 | #define RANDNET_LED_POS 1 100 | #define RANDNET_BTN_POS 2 101 | #define RANDNET_MAX_BUTTONS 3 102 | #define RANDNET_LED_NUMLOCK (1 << 0) 103 | #define RANDNET_LED_CAPSLOCK (1 << 1) 104 | #define RANDNET_LED_POWER (1 << 2) 105 | #define RANDNET_FLAG_HOME_KEY (1 << 0) 106 | #define RANDNET_FLAG_EXCESS_BUTTONS (1 << 4) 107 | 108 | //Mempak 109 | #define MEMPAK_SIZE 32768 110 | #define MAX_MEMPAKS MAX_CONTROLLERS 111 | #define VIRTUAL_PAK MAX_MEMPAKS 112 | 113 | void n64_subsystem_init(n64_input_dev_t *in_dev); 114 | void n64_controller_hande_new_edge(n64_input_dev_t *cont); 115 | 116 | #ifdef __cplusplus 117 | } 118 | #endif 119 | 120 | #endif 121 | -------------------------------------------------------------------------------- /src/n64/n64_mempak.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include "n64_mempak.h" 6 | #include "n64_virtualpak.h" 7 | #include "n64_settings.h" 8 | #include "n64_transferpak_gbcarts.h" 9 | #include "n64_controller.h" 10 | #include "n64_wrapper.h" 11 | 12 | void n64_mempack_read32(n64_mempack *mempack, uint16_t address, uint8_t *rx_buff) 13 | { 14 | if (mempack->virtual_is_active) 15 | { 16 | n64_virtualpak_read32(address, rx_buff); 17 | } 18 | else 19 | { 20 | n64hal_read_extram(rx_buff, mempack->data, address, 32); 21 | } 22 | } 23 | 24 | void n64_mempack_write32(n64_mempack *mempack, uint16_t address, uint8_t *tx_buff) 25 | { 26 | if (mempack->virtual_is_active) 27 | { 28 | n64_virtualpak_write32(address, tx_buff); 29 | } 30 | else 31 | { 32 | n64hal_write_extram(tx_buff, mempack->data, address, 32); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/n64/n64_mempak.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #ifndef _N64_MEMPAK_h 5 | #define _N64_MEMPAK_h 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #define MEMPAK_SIZE 32768 12 | 13 | typedef struct 14 | { 15 | uint32_t id; 16 | uint8_t *data; 17 | //Only if a 'virtual mempak' 18 | uint32_t virtual_is_active; 19 | uint32_t virtual_update_req; 20 | uint32_t virtual_selected_row; 21 | } n64_mempack; 22 | 23 | void n64_mempack_read32(n64_mempack *mempack, uint16_t address, uint8_t *rx_buff); 24 | void n64_mempack_write32(n64_mempack *mempack, uint16_t address, uint8_t *tx_buff); 25 | 26 | #ifdef __cplusplus 27 | } 28 | #endif 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/n64/n64_rumblepak.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | //Nothing here yet, see n64_rumblepak.h -------------------------------------------------------------------------------- /src/n64/n64_rumblepak.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #ifndef N64_N64_RUMBLEPAK_H_ 5 | #define N64_N64_RUMBLEPAK_H_ 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | enum n64_rumble_state 12 | { 13 | RUMBLE_STOP = 0, 14 | RUMBLE_START, 15 | RUMBLE_APPLIED 16 | }; 17 | 18 | typedef struct 19 | { 20 | int8_t initialised; 21 | enum n64_rumble_state state; 22 | } n64_rumblepak; 23 | 24 | #ifdef __cplusplus 25 | } 26 | #endif 27 | 28 | #endif /* N64_N64_RUMBLEPAK_H_ */ 29 | -------------------------------------------------------------------------------- /src/n64/n64_settings.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include "n64_settings.h" 6 | #include "n64_wrapper.h" 7 | #include "printf.h" 8 | 9 | n64_settings *_settings = NULL; 10 | 11 | static uint8_t _calc_checksum(n64_settings *settings) 12 | { 13 | uint8_t checksum = 0; 14 | for (int i = 0; i < sizeof(n64_settings) - 1; i++) 15 | { 16 | checksum += ((uint8_t *)settings)[i]; 17 | } 18 | return checksum; 19 | } 20 | 21 | //*settings should be a globally available allocated block of memory 22 | void n64_settings_init(n64_settings *settings) 23 | { 24 | if (settings == NULL) 25 | return; 26 | 27 | if (settings->start != 0x64 || settings->checksum != _calc_checksum(settings)) 28 | { 29 | debug_print_error("[N64 SETTINGS] ERROR: %s not found or invalid, setting to default\n", SETTINGS_FILENAME); 30 | memset(settings, 0x00, sizeof(n64_settings)); 31 | for (uint32_t i = 0; i < MAX_CONTROLLERS; i++) 32 | { 33 | settings->start = 0x64; //N64 :) 34 | settings->deadzone[i] = DEFAULT_DEADZONE; 35 | settings->sensitivity[i] = DEFAULT_SENSITIVITY; 36 | settings->snap_axis[i] = DEFAULT_SNAP; 37 | settings->octa_correct[i] = DEFAULT_OCTA_CORRECT; 38 | } 39 | n64_settings_update_checksum(settings); 40 | } 41 | _settings = settings; 42 | } 43 | 44 | void n64_settings_update_checksum(n64_settings *settings) 45 | { 46 | settings->checksum = _calc_checksum(settings); 47 | debug_print_n64("[N64 SETTINGS] Settings updated, new checksum %02x\n", settings->checksum); 48 | 49 | //Mark the memory as dirty. 50 | uint8_t start = 0x64; 51 | n64hal_write_extram(&start, settings, 0, 1); 52 | } 53 | 54 | n64_settings *n64_settings_get() 55 | { 56 | if (_settings == NULL) 57 | { 58 | debug_print_error("[N64 SETTINGS] ERROR: n64_settings_get returning null. Did you n64_settings_init?\n"); 59 | } 60 | return _settings; 61 | } 62 | -------------------------------------------------------------------------------- /src/n64/n64_settings.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #ifndef N64_N64_SETTINGS_H_ 5 | #define N64_N64_SETTINGS_H_ 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #include "usb64_conf.h" 12 | 13 | typedef struct 14 | { 15 | uint8_t start; 16 | char default_tpak_rom[MAX_CONTROLLERS][MAX_FILENAME_LEN]; //Filename 17 | uint8_t sensitivity[MAX_CONTROLLERS]; //0 to 4 18 | uint8_t deadzone[MAX_CONTROLLERS]; //0 to 4 19 | uint8_t snap_axis[MAX_CONTROLLERS]; //0 or 1 20 | uint8_t octa_correct[MAX_CONTROLLERS]; //0 or 1 21 | uint8_t checksum; 22 | } n64_settings; 23 | 24 | void n64_settings_init(n64_settings* settings); 25 | void n64_settings_update_checksum(n64_settings *settings); 26 | n64_settings *n64_settings_get(); 27 | 28 | #ifdef __cplusplus 29 | } 30 | #endif 31 | 32 | #endif /* N64_N64_SETTINGS_H_ */ 33 | -------------------------------------------------------------------------------- /src/n64/n64_transferpak_gbcarts.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | /* gb_write_cart and gb_read_cart adapted from https://github.com/deltabeard/Peanut-GB/ which is shared 5 | * under the MIT license 6 | * All other gamecart info from https://gbdev.gg8.se/wiki/articles/Main_Page 7 | * Tranferpak emulation is my own RE. 8 | */ 9 | 10 | #include 11 | #include "printf.h" 12 | #include "n64_mempak.h" 13 | #include "n64_virtualpak.h" 14 | #include "n64_settings.h" 15 | #include "n64_transferpak_gbcarts.h" 16 | #include "n64_controller.h" 17 | #include "n64_wrapper.h" 18 | 19 | static uint32_t _gb_get_rom_size(uint8_t rom_type) 20 | { 21 | uint32_t rom_len = 0; 22 | switch (rom_type) 23 | { 24 | case (KB_32): rom_len = 32768UL; break; 25 | case (KB_64): rom_len = 65536UL; break; 26 | case (KB_128): rom_len = 131072UL; break; 27 | case (KB_256): rom_len = 262144UL; break; 28 | case (KB_512): rom_len = 524288UL; break; 29 | case (KB_1024): rom_len = 1048576UL; break; 30 | case (KB_2048): rom_len = 2097152UL; break; 31 | case (KB_4096): rom_len = 4194304UL; break; 32 | case (KB_8192): rom_len = 8388608UL; break; 33 | case (KB_1152): rom_len = 1179648UL; break; 34 | case (KB_1280): rom_len = 1310720UL; break; 35 | case (KB_1536): rom_len = 1572864UL; break; 36 | default: 37 | rom_len = 0; 38 | debug_print_error("[TPAK] ERROR: Unknown ROM size\n"); 39 | break; 40 | } 41 | return rom_len; 42 | } 43 | 44 | static uint32_t _gb_get_ram_size(uint8_t sram_type, uint8_t mbc_type) 45 | { 46 | uint32_t sram_len = 0; 47 | switch (sram_type) 48 | { 49 | case (B_2048): sram_len = 2048; break; 50 | case (B_8192): sram_len = 8192; break; 51 | case (B_32768): sram_len = 32768; break; 52 | case (B_65536): sram_len = 65536; break; 53 | case (B_131072): sram_len = 131072; break; 54 | default: sram_len = 0; break; 55 | } 56 | 57 | //MBC2 chip contains 512x4 bits of built in RAM. 58 | if (mbc_type == MBC2 || mbc_type == MBC2_BAT) 59 | { 60 | sram_len = 256; 61 | } 62 | return sram_len; 63 | } 64 | 65 | //Returns a plain integer of the mbc. 66 | static uint8_t _gb_get_mbc_number(uint8_t mbc_type) 67 | { 68 | switch (mbc_type) 69 | { 70 | case MBC1: 71 | case MBC1_RAM: 72 | case MBC1_RAM_BAT: 73 | return 1; 74 | case MBC2: 75 | case MBC2_BAT: 76 | return 2; 77 | case MBC3: 78 | case MBC3_RAM: 79 | case MBC3_RAM_BAT: 80 | case MBC3_TIM_BAT: 81 | case MBC3_TIM_RAM_BAT: 82 | return 3; 83 | case MBC4: 84 | case MBC4_RAM: 85 | case MBC4_RAM_BAT: 86 | return 4; 87 | case MBC5: 88 | case MBC5_RAM: 89 | case MBC5_RAM_BAT: 90 | case MBC5_RUM: 91 | case MBC5_RUM_RAM: 92 | case MBC5_RUM_RAM_BAT: 93 | return 5; 94 | default: 95 | return 0; 96 | } 97 | } 98 | 99 | static void gb_write_cart(uint16_t addr, gameboycart *gb, uint8_t *inBuffer) 100 | { 101 | uint8_t mbc = _gb_get_mbc_number(gb->mbc); 102 | uint8_t val = inBuffer[31]; 103 | switch (addr >> 12) 104 | { 105 | case 0x0: 106 | case 0x1: 107 | if (mbc == 2 && addr & 0x10) 108 | { 109 | return; 110 | } 111 | else if (mbc > 0 && gb->ramsize > 0) 112 | { 113 | gb->enable_cart_ram = ((val & 0x0F) == 0x0A); 114 | debug_print_tpak("[TPAK] MBC%u - Enable Cart Ram\n", mbc); 115 | } 116 | return; 117 | 118 | case 0x2: 119 | if (mbc == 5) 120 | { 121 | //MBC5 lower ROM bank byte is set 122 | gb->selected_rom_bank = (gb->selected_rom_bank & 0x100) | val; 123 | gb->selected_rom_bank = gb->selected_rom_bank % gb->num_rom_banks; 124 | debug_print_tpak("[TPAK] MBC%u - ROM Bank changed to %u/%u\n", mbc, 125 | gb->selected_rom_bank, 126 | gb->num_rom_banks); 127 | return; 128 | } 129 | 130 | /* Intentional fall through. */ 131 | 132 | case 0x3: 133 | if (mbc == 1) 134 | { 135 | //selected_rom_bank = val & 0x7; 136 | gb->selected_rom_bank = (val & 0x1F) | (gb->selected_rom_bank & 0x60); 137 | 138 | if ((gb->selected_rom_bank & 0x1F) == 0x00) 139 | gb->selected_rom_bank++; 140 | } 141 | else if (mbc == 2 && addr & 0x10) 142 | { 143 | gb->selected_rom_bank = val & 0x0F; 144 | 145 | if (!gb->selected_rom_bank) 146 | gb->selected_rom_bank++; 147 | } 148 | else if (mbc == 3) 149 | { 150 | gb->selected_rom_bank = val & 0x7F; 151 | 152 | if (!gb->selected_rom_bank) 153 | gb->selected_rom_bank++; 154 | } 155 | else if (mbc == 5) 156 | { 157 | //MBC5 has a 9th ROM bank bit 158 | gb->selected_rom_bank = ((val & 0x01) << 8) | (gb->selected_rom_bank & 0xFF); 159 | } 160 | gb->selected_rom_bank = gb->selected_rom_bank % gb->num_rom_banks; 161 | 162 | debug_print_tpak("[TPAK] MBC%u - ROM Bank changed to %u/%u\n", mbc, 163 | gb->selected_rom_bank, 164 | gb->num_rom_banks); 165 | return; 166 | 167 | case 0x4: 168 | case 0x5: 169 | if (mbc == 1) 170 | { 171 | gb->selected_ram_bank = (val & 3); 172 | gb->selected_rom_bank = ((val & 3) << 5) | (gb->selected_rom_bank & 0x1F); 173 | gb->selected_rom_bank = gb->selected_rom_bank % gb->num_rom_banks; 174 | } 175 | else if (mbc == 3) 176 | { 177 | gb->selected_ram_bank = val; 178 | } 179 | else if (mbc == 5) 180 | { 181 | gb->selected_ram_bank = (val & 0x0F); 182 | } 183 | debug_print_tpak("[TPAK] MBC%u - RAM Bank changed to %u\n", mbc, gb->selected_ram_bank); 184 | return; 185 | 186 | case 0x6: 187 | case 0x7: 188 | gb->cart_mode_select = (val & 1); 189 | debug_print_tpak("[TPAK] MBC%u - Cart Mode changed to %u\n", mbc, gb->cart_mode_select); 190 | return; 191 | 192 | case 0xA: 193 | case 0xB: 194 | if (gb->ramsize > 0 && gb->enable_cart_ram) 195 | { 196 | if (mbc == 3 && gb->selected_ram_bank >= 0x08) 197 | { 198 | gb->rtc[gb->selected_ram_bank - 0x08] = val; 199 | n64hal_rtc_write(&gb->rtc_bits.high, &gb->rtc_bits.yday, 200 | &gb->rtc_bits.hour, &gb->rtc_bits.min, &gb->rtc_bits.sec); 201 | debug_print_tpak("[TPAK] MBC%u - RTC Write Reg %02x, Val: %u\n", mbc, gb->selected_ram_bank, val); 202 | } 203 | else if (gb->cart_mode_select && gb->selected_ram_bank < gb->num_ram_banks) 204 | { 205 | n64hal_write_extram(inBuffer, gb->ram, addr - CART_RAM_ADDR + (gb->selected_ram_bank * CRAM_BANK_SIZE), 32); 206 | } 207 | else if (gb->num_ram_banks) 208 | { 209 | n64hal_write_extram(inBuffer, gb->ram, addr - CART_RAM_ADDR, 32); 210 | } 211 | } 212 | return; 213 | } 214 | return; 215 | } 216 | 217 | static void gb_read_cart(uint16_t addr, gameboycart *gb, uint8_t *outBuffer) 218 | { 219 | uint8_t mbc = _gb_get_mbc_number(gb->mbc); 220 | switch (addr >> 12) 221 | { 222 | case 0x0: 223 | case 0x1: 224 | case 0x2: 225 | case 0x3: 226 | n64hal_read_extram(outBuffer, gb->rom, addr, 32); 227 | return; 228 | 229 | case 0x4: 230 | case 0x5: 231 | case 0x6: 232 | case 0x7: 233 | if (mbc == 1 && gb->cart_mode_select) 234 | { 235 | n64hal_read_extram(outBuffer, gb->rom, addr + ((gb->selected_rom_bank & 0x1F) - 1) * ROM_BANK_SIZE, 32); 236 | } 237 | else 238 | { 239 | n64hal_read_extram(outBuffer, gb->rom, addr + (gb->selected_rom_bank - 1) * ROM_BANK_SIZE, 32); 240 | } 241 | return; 242 | 243 | case 0xA: 244 | case 0xB: 245 | if (gb->ramsize > 0 && gb->enable_cart_ram) 246 | { 247 | if (mbc == 3 && gb->selected_ram_bank >= 0x08) 248 | { 249 | n64hal_rtc_read(&gb->rtc_bits.high, &gb->rtc_bits.yday, 250 | &gb->rtc_bits.hour, &gb->rtc_bits.min, &gb->rtc_bits.sec); 251 | memset(outBuffer, gb->rtc[gb->selected_ram_bank - 0x08], 32); 252 | debug_print_tpak("[TPAK] MBC%u - RTC Read Reg %02x\n", mbc, gb->selected_ram_bank); 253 | } 254 | else if ((gb->cart_mode_select || mbc != 1) && gb->selected_ram_bank < gb->num_ram_banks) 255 | { 256 | n64hal_read_extram(outBuffer, gb->ram, addr - CART_RAM_ADDR + (gb->selected_ram_bank * CRAM_BANK_SIZE), 32); 257 | } 258 | else 259 | { 260 | n64hal_read_extram(outBuffer, gb->ram, addr - CART_RAM_ADDR, 32); 261 | } 262 | } 263 | return; 264 | } 265 | return; 266 | } 267 | 268 | //Get the actual cart MBC address from the address sent to the tpak. 269 | static uint16_t _tpak_get_mbc_address(uint16_t tpakAddress, uint8_t bank) 270 | { 271 | return ((tpakAddress & 0xFFE0) - 0xC000) + ((bank & 3) * 0x4000); 272 | } 273 | 274 | void tpak_write(n64_transferpak *tp, uint16_t raw_address, uint8_t *data) 275 | { 276 | uint16_t mbc_address = _tpak_get_mbc_address(raw_address, tp->selected_mbc_bank); 277 | gb_write_cart(mbc_address, tp->gbcart, data); 278 | } 279 | 280 | void tpak_read(n64_transferpak *tp, uint16_t raw_address, uint8_t *data) 281 | { 282 | uint16_t mbc_address = _tpak_get_mbc_address(raw_address, tp->selected_mbc_bank); 283 | gb_read_cart(mbc_address, tp->gbcart, data); 284 | } 285 | 286 | void tpak_reset(n64_transferpak *tpak) 287 | { 288 | tpak->access_state = 0; 289 | tpak->access_state_changed = 0; 290 | tpak->selected_mbc_bank = 0; 291 | tpak->power_state = 0; 292 | } 293 | 294 | void gb_init_cart(gameboycart *cart, uint8_t *gb_header, char *filename) 295 | { 296 | //Fixed header area in all valid gameboy roms. 297 | const uint8_t GB_HEADER_LOGO[] = {0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 298 | 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 299 | 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 300 | 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, 301 | 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 302 | 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E}; 303 | 304 | if (memcmp(GB_HEADER_LOGO, &gb_header[GB_LOGO_OFFSET - 0x100], 0x18) != 0) 305 | { 306 | debug_print_error("[TPAK] ERROR: gb_init_cart: GB header not valid\n"); 307 | return; 308 | } 309 | 310 | memset(cart->title, '\0', 15); 311 | memcpy(cart->title, &gb_header[GB_TITLE_OFFSET - 0x100], 15); 312 | cart->mbc = gb_header[GB_MBCTYPE_OFFSET - 0x100]; 313 | cart->romsize = _gb_get_rom_size(gb_header[GB_ROMSIZE_OFFSET - 0x100]); 314 | cart->ramsize = _gb_get_ram_size(gb_header[GB_RAMSIZE_OFFSET - 0x100], cart->mbc); 315 | cart->selected_rom_bank = 1; 316 | cart->selected_ram_bank = 0; 317 | cart->enable_cart_ram = 0; 318 | cart->cart_mode_select = 0; 319 | cart->num_ram_banks = cart->ramsize / CRAM_BANK_SIZE; 320 | cart->num_rom_banks = cart->romsize / ROM_BANK_SIZE; 321 | 322 | memcpy(cart->filename, filename, sizeof(cart->filename)); 323 | debug_print_tpak("[TPAK] gb_init_cart: GB Name: %.15s\n", cart->title); 324 | debug_print_tpak("[TPAK] gb_init_cart: ROM Bytes: %lu\n", cart->romsize); 325 | debug_print_tpak("[TPAK] gb_init_cart: SRAM Bytes: %lu\n", cart->ramsize); 326 | debug_print_tpak("[TPAK] gb_init_cart: MBC Type: 0x%02x\n", cart->mbc); 327 | } 328 | 329 | uint8_t gb_has_battery(uint8_t mbc_type) 330 | { 331 | switch (mbc_type) 332 | { 333 | case MBC1_RAM_BAT: 334 | case MBC2_BAT: 335 | case MBC3_RAM_BAT: 336 | case MBC3_TIM_BAT: 337 | case MBC3_TIM_RAM_BAT: 338 | case MBC4_RAM_BAT: 339 | case MBC5_RAM_BAT: 340 | case MBC5_RUM_RAM_BAT: 341 | return 1; 342 | } 343 | return 0; 344 | } 345 | 346 | //TODO:? 347 | //Kept my RE notes here for future ref 348 | void gb_set_pokemon_time(gameboycart *cart) 349 | { 350 | /* Pokemon game time works by storing a base time in the saved game located from address 0x2044 to 0x2047 351 | * The base time is set at the beginning of a new game when prompted for the Day, Hour and Minute by\ 352 | * Prof. Oak/Mum and it never changes as long as the saved game exists. 353 | * When you load the saved game, the cart reads the RTC registers then adds the RTC offset to the base time to determine 354 | * the actual game time. After startup the gameboy internal clocks area used to track time when the power is on. 355 | * 356 | * The Base Time which is set when you start a new game by the prompts from Prof Oak are stored in the below SRAM Offsets: 357 | * 0x2044 = Day (Values can be 0x00 to 0x06 = Sunday to Saturday) 358 | * 0x2045 = Hour (Values can be 0x00 to 0x18 = 1AM to 12PM) 359 | * 0x2046 = Minutes (Values can be 0x00 to 0x3B = 0 to 59 Minutes) 360 | * 0x2047 = Seconds (Values can be 0x00 to 0x3B = 0 to 59 Minutes) 361 | * 362 | * Address 0x2049 to 0x204C contain the actual game time after the Gameboy adds the RTC offset to the base time. 363 | * This updates every time you save, modifying them doesn't do anything as its just written over by the 364 | * Gameboy/Emulator when you save. 365 | * 366 | * As the Gameboy only reads the base time and the RTC registers on startup there is no way to change the time mid game 367 | * even if you hack new values into the SRAM or RTC. The game only reads them at startup! 368 | * A real RTC will keep running in the background even when powered down, so the next time you turn the game on it reads 369 | * the RTC registers and recalculate the actual game time to have time pass even when the game is off. 370 | * Emulators often save the RTC registers and reloads them next time you start the game to resume it left off next time 371 | * you start playing. 372 | * 373 | * So the only way to modify the day/time in the game is to do it before the game starts. 374 | * It will then load in the modified values and won't know any different! 375 | * To prevent having to modify the actual save file which probably isn't a good idea generally, 376 | * you can also modify the RTC registers which are added to the base time values by the Gameboy itself. 377 | * This is what this function does! 378 | * 379 | */ 380 | /* 381 | uint8_t baseDay, baseHour, baseMin, baseSec; 382 | memcpy(&baseDay, cart->ram + 0x2044, 1); 383 | memcpy(&baseHour, cart->ram + 0x2045, 1); 384 | memcpy(&baseMin, cart->ram + 0x2046, 1); 385 | memcpy(&baseSec, cart->ram + 0x2047, 1); 386 | 387 | debug_print_tpak("[TPAK] Base d:%u h:%u m:%u s:%u\n",baseDay,baseHour,baseMin,baseSec); 388 | 389 | uint16_t d; 390 | uint8_t h, m, s; 391 | n64hal_rtc_read(&d, &h, &m, &s); 392 | 393 | debug_print_tpak("[TPAK] Actual d:%u h:%u m:%u s:%u\n",d,h,m,s); 394 | */ 395 | } 396 | -------------------------------------------------------------------------------- /src/n64/n64_transferpak_gbcarts.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #ifndef _N64_TRANSFERPAK_GBCARTS_h 5 | #define _N64_TRANSFERPAK_GBCARTS_h 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | //TPAK 12 | #define TPAK_STATUS_READY 0x01 13 | #define TPAK_STATUS_WAS_RESET 0x04 14 | #define TPAK_STATUS_IS_RESETTING 0x08 15 | #define TPAK_STATUS_REMOVED 0x40 16 | #define TPAK_STATUS_POWERED 0x80 17 | 18 | //GAMEBOY 19 | //Header offsets - ref https://web.archive.org/web/20141105020940/http://problemkaputt.de/pandocs.htm 20 | #define GB_LOGO_OFFSET 0x0104 //48 bytes 21 | #define GB_TITLE_OFFSET 0x0134 //16 bytes 22 | #define GB_MANUF_OFFSET 0x013F //4 bytes 23 | #define GB_MBCTYPE_OFFSET 0x0147 //1 byte 24 | #define GB_ROMSIZE_OFFSET 0x0148 //1 byte 25 | #define GB_RAMSIZE_OFFSET 0x0149 //1 byte 26 | 27 | //Cart Types 28 | #define ROM_ONLY 0x00 29 | #define ROM_RAM 0x08 30 | #define ROM_RAM_BAT 0x09 31 | #define MBC1 0x01 32 | #define MBC1_RAM 0x02 33 | #define MBC1_RAM_BAT 0x03 34 | #define MBC2 0x05 35 | #define MBC2_BAT 0x06 36 | #define MBC3 0x11 37 | #define MBC3_RAM 0x12 38 | #define MBC3_RAM_BAT 0x13 39 | #define MBC3_TIM_BAT 0x0F 40 | #define MBC3_TIM_RAM_BAT 0x10 41 | #define MBC4 0x15 42 | #define MBC4_RAM 0x16 43 | #define MBC4_RAM_BAT 0x17 44 | #define MBC5 0x19 45 | #define MBC5_RAM 0x1A 46 | #define MBC5_RAM_BAT 0x1B 47 | #define MBC5_RUM 0x1C 48 | #define MBC5_RUM_RAM 0x1D 49 | #define MBC5_RUM_RAM_BAT 0x1E 50 | 51 | //ROM Sizes 52 | #define KB_32 0x00 53 | #define KB_64 0x01 54 | #define KB_128 0x02 55 | #define KB_256 0x03 56 | #define KB_512 0x04 57 | #define KB_1024 0x05 58 | #define KB_2048 0x06 59 | #define KB_4096 0x07 60 | #define KB_8192 0x08 61 | #define KB_1152 0x52 62 | #define KB_1280 0x53 63 | #define KB_1536 0x54 64 | 65 | //RAM Sizes 66 | #define NONE 0x00 //If MBC2, 00 includes a built-in RAM of 512 x 4 bits. 67 | #define B_2048 0x01 68 | #define B_8192 0x02 69 | #define B_32768 0x03 70 | #define B_131072 0x04 71 | #define B_65536 0x05 72 | 73 | //MBC 74 | #define ROM_BANK_SIZE 0x4000 75 | #define CRAM_BANK_SIZE 0x2000 76 | #define CART_RAM_ADDR 0xA000 77 | 78 | typedef struct 79 | { 80 | char title[16]; //The cart game title extracted from the ROM header 81 | 82 | //Data 83 | uint32_t romsize; //The cart rom size extracted from the ROM header 84 | uint32_t ramsize; //The cart ram size extracted from the ROM header 85 | uint8_t *ram; //Pointer to RAM data (if used) 86 | uint8_t *rom; //Pointer to rom data (if used) 87 | 88 | //MBC 89 | uint32_t mbc; //What mbc the cart uses, extracted from the ROM header 90 | uint32_t selected_rom_bank; 91 | uint32_t selected_ram_bank; 92 | uint32_t enable_cart_ram; 93 | uint32_t cart_mode_select; 94 | uint32_t num_ram_banks; 95 | uint32_t num_rom_banks; 96 | 97 | //RTC 98 | union 99 | { 100 | struct 101 | { 102 | uint8_t sec; //sec 0-59 (0-3Bh) 103 | uint8_t min; //min 0-59 (0-3Bh) 104 | uint8_t hour; //hour 0-23 (0-17h) 105 | uint8_t yday; //high | yday 0-365. 9 bit register. 106 | uint8_t high; 107 | } rtc_bits; 108 | uint8_t rtc[5]; 109 | }; 110 | 111 | //FATFS 112 | char filename[256]; //Filename of ROM in FATFS flash 113 | } gameboycart; 114 | 115 | typedef struct 116 | { 117 | uint32_t power_state; //Writing 0x84 to 0x8001 turns this on. Writing 0xFE to 0x8001 turns this off 118 | uint32_t access_state; //Writing 0x01 to 0xB010 turns this on. Writing 0x00 to 0xB010 turns this off. 119 | uint32_t access_state_changed; //This is set if the access_state was just changed. 120 | uint32_t selected_mbc_bank; //Banks to access the overall MBC space. 0x0000 up to 0x7FFF. 121 | gameboycart *gbcart; 122 | } n64_transferpak; 123 | 124 | //Prototypes 125 | void tpak_write(n64_transferpak *tp, uint16_t raw_address, uint8_t *data); 126 | void tpak_read(n64_transferpak *tp, uint16_t raw_address, uint8_t *data); 127 | void tpak_reset(n64_transferpak *tpak); 128 | 129 | void gb_init_cart(gameboycart *cart, uint8_t *gb_header, char *filename); 130 | uint8_t gb_has_battery(uint8_t mbc_type); 131 | 132 | #ifdef __cplusplus 133 | } 134 | #endif 135 | 136 | #endif 137 | -------------------------------------------------------------------------------- /src/n64/n64_virtualpak.c: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include "printf.h" 6 | #include "usb64_conf.h" 7 | #include "n64_mempak.h" 8 | #include "n64_virtualpak.h" 9 | #include "n64_settings.h" 10 | #include "n64_transferpak_gbcarts.h" 11 | #include "n64_controller.h" 12 | #include "n64_wrapper.h" 13 | 14 | #define HEADING MENU_LINE1 15 | #define SUBHEADING MENU_LINE2 16 | 17 | #define MENU_TPAK MENU_LINE4 18 | #define MENU_CONTROLLER_SETTINGS MENU_LINE5 19 | #define MENU_INFO0 MENU_LINE6 20 | #define MENU_INFO1 MENU_LINE7 21 | 22 | #define CHANGE_CONTROLLER MENU_LINE15 23 | #define MENU_MAIN MENU_LINE16 24 | #define RETURN MENU_MAIN 25 | 26 | static uint32_t controller_page = 0; 27 | static uint32_t current_menu = MENU_MAIN; 28 | static uint32_t num_roms = 0; 29 | static char buff[64]; 30 | static char *gbrom_filenames[MAX_GBROMS] = {NULL}; //Gameboy ROM file name list 31 | static char *gbrom_titlenames[MAX_GBROMS] = {NULL}; //Gameboy ROM cart title list 32 | 33 | static char info_text_0[256]; 34 | static char info_text_1[256]; 35 | 36 | //First 32 bytes of mempak. First byte must be 0x81. This small section is in RAM as the console writes to it 37 | uint8_t n64_virtualpak_scratch[0x20] = { 38 | 0x81, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 39 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F}; 40 | 41 | //First 0x300 bytes of mempak. I took this from a mempak I generated on my n64. 42 | //const as the console only needs to read from this area 43 | const uint8_t n64_virtualpak_header[0x300] = { 44 | 0x81, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 45 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 46 | 0xFF, 0xFF, 0xFF, 0xFF, 0x05, 0x1A, 0x5F, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 47 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0x66, 0x25, 0x99, 0xCD, 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 49 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 50 | 0xFF, 0xFF, 0xFF, 0xFF, 0x05, 0x1A, 0x5F, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 51 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0x66, 0x25, 0x99, 0xCD, 52 | 0xFF, 0xFF, 0xFF, 0xFF, 0x05, 0x1A, 0x5F, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0x66, 0x25, 0x99, 0xCD, 54 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 55 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 56 | 0xFF, 0xFF, 0xFF, 0xFF, 0x05, 0x1A, 0x5F, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 57 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0x66, 0x25, 0x99, 0xCD, 58 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 59 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 60 | 0x00, 0x51, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 61 | 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 62 | 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 63 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 64 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 65 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 66 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 67 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 68 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 69 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 70 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 71 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 72 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 73 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 74 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 75 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 76 | 0x00, 0x51, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 77 | 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 78 | 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 79 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 80 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 81 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 82 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 83 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 84 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 85 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 86 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 87 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 88 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 89 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 90 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 91 | 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03}; 92 | 93 | //This is the notesTable located at address 0x300 to 0x500 in the mempack address space. 94 | //This initialises the title blocks as all blank titles using 1 page each. 95 | //Basically what is displayed on the mempak manager page. 96 | /* 0x4E = N (Media Type Cartridge) 97 | * 0x41 = R Game Name Code of sorts. ZL = Zelda, PO = Pokemon Snap. 98 | * 0x43 = Y RY = Ryan :) 99 | * 0x45 = E Region "North America" 100 | * 0x35 = 5 Publisher Code 101 | * 0x48 = H Publisher Code 102 | * 0x00 = Always 0x00? 103 | * 0x05 = Between 5 and 127, Page Index 104 | * 0x02 = ? 105 | * 0x03 = ? 106 | */ 107 | uint8_t n64_virtualpak_note_table[0x200] = { 108 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x05, 0x02, 0x03, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 109 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 110 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x06, 0x02, 0x03, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 111 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 112 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x07, 0x02, 0x03, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 113 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 114 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x08, 0x02, 0x03, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 115 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 116 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x09, 0x02, 0x03, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 117 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 118 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x0A, 0x02, 0x03, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 119 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 120 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x0B, 0x02, 0x03, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 121 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 122 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x0C, 0x02, 0x03, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 123 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 124 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x0D, 0x02, 0x03, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 125 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 126 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x0E, 0x02, 0x03, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 127 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 128 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x0F, 0x02, 0x03, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 129 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 130 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x10, 0x02, 0x03, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 131 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 132 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x11, 0x02, 0x01, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 133 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 134 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x12, 0x02, 0x01, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 135 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 136 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x13, 0x02, 0x01, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 137 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D, 138 | 0x4E, 0x41, 0x43, 0x45, 0x35, 0x48, 0x00, 0x14, 0x02, 0x01, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 139 | 0x1A, 0x2B, 0x26, 0x32, 0x26, 0x1E, 0x27, 0x1A, 0x22, 0x2B, 0x1C, 0x28, 0x26, 0x1B, 0x1A, 0x2D}; 140 | 141 | /* 142 | * Function: Converts and writes an ASCII string to the mempak note table 143 | * ---------------------------- 144 | * Returns void 145 | * 146 | * msg: Pointer to a null terminated ASCII string 147 | * line: What line to write the string in the mempak menu [0 to 15] 148 | * ext: Setting to 1 will write the string to the extension section of each title. (4 chars wide) 149 | */ 150 | static void n64_virtualpak_write_string(char *msg, uint8_t line, uint8_t ext) 151 | { 152 | //Obtained from pulling known save titles and a bit of trial and error 153 | static const uint8_t MEMPACK_CHARMAP[] = 154 | { 155 | '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 156 | ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 157 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 158 | 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 159 | '!', '"', '#', '\'', '*', '+', ',', '-', '.', '/', ':', '=', '?', '@' 160 | }; 161 | 162 | uint32_t max_len; 163 | uint32_t len = 255; 164 | 165 | (ext == 1) ? (max_len = 4) : (max_len = 16); 166 | 167 | for (uint32_t i = 0; i < max_len; i++) 168 | { 169 | uint8_t n64char = 0; 170 | 171 | //If string terminator, fix length 172 | if (msg[i] == '\0') 173 | len = i; 174 | 175 | //Force upper case 176 | if (msg[i] > 96) 177 | msg[i] -= 32; 178 | 179 | //Handle some unique cases 180 | if (msg[i] == '_') 181 | msg[i] = '-'; //replace _ with - 182 | 183 | //Find a match in the CHARMAP 184 | for (uint32_t j = 0; j < sizeof(MEMPACK_CHARMAP); j++) 185 | { 186 | if (msg[i] == MEMPACK_CHARMAP[j]) 187 | n64char = j; 188 | } 189 | 190 | //Pad with spaces if past end or the character is unsupported 191 | if (i > len || n64char == 0) 192 | n64char = 15; 193 | 194 | //Sanity check this 195 | if (line > 15) 196 | line = 15; 197 | 198 | //Write the character to the note table 199 | if (ext) 200 | n64_virtualpak_note_table[(32 * line) + 12 + i] = n64char; 201 | else 202 | n64_virtualpak_note_table[(32 * line) + 16 + i] = n64char; 203 | } 204 | } 205 | 206 | void n64_virtualpak_init(n64_mempack *vpak) 207 | { 208 | vpak->virtual_is_active = 1; 209 | vpak->virtual_selected_row = MENU_MAIN; 210 | current_menu = MENU_MAIN; 211 | 212 | //Clear up any previous memory allocations 213 | for (uint32_t i = 0; i < num_roms; i++) 214 | { 215 | if (gbrom_filenames[i] != NULL) 216 | free(gbrom_filenames[i]); 217 | if (gbrom_titlenames[i] != NULL) 218 | free(gbrom_titlenames[i]); 219 | 220 | gbrom_filenames[i] = NULL; 221 | gbrom_titlenames[i] = NULL; 222 | } 223 | 224 | num_roms = n64hal_list_gb_roms(gbrom_filenames, MAX_GBROMS); 225 | 226 | //For each ROM, extract the ROM info and rom title 227 | for (uint32_t i = 0; i < num_roms; i++) 228 | { 229 | uint8_t gb_header[0x100]; 230 | gameboycart gb_cart; 231 | 232 | n64hal_read_storage(gbrom_filenames[i], 0x100, gb_header, sizeof(gb_header)); 233 | gb_init_cart(&gb_cart, gb_header, gbrom_filenames[i]); 234 | 235 | //Copy the gb cart title (from the rom header into an array) 236 | gbrom_titlenames[i] = (char *)malloc(strlen(gb_cart.title) + 1); 237 | strcpy(gbrom_titlenames[i], gb_cart.title); 238 | } 239 | n64_virtualpak_update(vpak); 240 | } 241 | 242 | void n64_virtualpak_read32(uint16_t address, uint8_t *rx_buff) 243 | { 244 | if (address < 0x20) 245 | memcpy(rx_buff, &n64_virtualpak_scratch[address], 32); 246 | else if (address < 0x300) 247 | memcpy(rx_buff, &n64_virtualpak_header[address], 32); 248 | else if (address < 0x500) 249 | memcpy(rx_buff, &n64_virtualpak_note_table[address - 0x300], 32); 250 | else 251 | memset(rx_buff, 0x00, 32); 252 | } 253 | 254 | void n64_virtualpak_write32(uint16_t address, uint8_t *tx_buff) 255 | { 256 | //If address is in scratch space, write it. 257 | //Ignore other writes. Dont actually want the N64 to write over stuff 258 | //If this scratch space doesnt get written, n64 assumes corrupt mempak. 259 | if (address < 0x20) 260 | memcpy(&n64_virtualpak_scratch[address], tx_buff, 32); 261 | } 262 | 263 | void n64_virtualpak_update(n64_mempack *vpak) 264 | { 265 | n64_settings *settings = n64_settings_get(); 266 | if (settings == NULL) 267 | return; 268 | 269 | //Clear the screen; 270 | char alpha[2] = {'A', '\0'}; 271 | for (uint32_t i = 0; i < 15; i++) 272 | { 273 | n64_virtualpak_write_string("-", i, MENU_NAME_FIELD); 274 | n64_virtualpak_write_string(alpha, i, MENU_EXT_FIELD); 275 | alpha[0]++; 276 | } 277 | 278 | //Handle generic header and footer options 279 | switch (vpak->virtual_selected_row) 280 | { 281 | case CHANGE_CONTROLLER: 282 | case SUBHEADING: 283 | controller_page++; 284 | if (controller_page >= MAX_CONTROLLERS) 285 | controller_page = 0; 286 | break; 287 | case HEADING: 288 | case RETURN: 289 | current_menu = MENU_MAIN; 290 | break; 291 | } 292 | 293 | //Print generic headers and footers 294 | n64_virtualpak_write_string("USB64 - RYZEE119", HEADING, MENU_NAME_FIELD); 295 | sprintf(buff, "CONTROLLER %u", controller_page + 1); 296 | n64_virtualpak_write_string(buff, SUBHEADING, MENU_NAME_FIELD); 297 | n64_virtualpak_write_string("________________", SUBHEADING + 1, MENU_NAME_FIELD); 298 | n64_virtualpak_write_string("CHANGE CONT", CHANGE_CONTROLLER, MENU_NAME_FIELD); 299 | n64_virtualpak_write_string("RETURN", RETURN, MENU_NAME_FIELD); 300 | 301 | /* Print the required menu and handle actions specific to each menu */ 302 | if (current_menu == MENU_MAIN) 303 | { 304 | switch (vpak->virtual_selected_row) 305 | { 306 | case MENU_TPAK: 307 | current_menu = MENU_TPAK; 308 | break; 309 | case MENU_CONTROLLER_SETTINGS: 310 | current_menu = MENU_CONTROLLER_SETTINGS; 311 | break; 312 | case MENU_INFO0: 313 | current_menu = MENU_INFO0; 314 | break; 315 | case MENU_INFO1: 316 | current_menu = MENU_INFO1; 317 | break; 318 | default: 319 | n64_virtualpak_write_string("TPAK SETTINGS", MENU_TPAK, MENU_NAME_FIELD); 320 | n64_virtualpak_write_string("CONT SETTINGS", MENU_CONTROLLER_SETTINGS, MENU_NAME_FIELD); 321 | n64_virtualpak_write_string("USB64 INFO1", MENU_INFO0, MENU_NAME_FIELD); 322 | n64_virtualpak_write_string("USB64 INFO2", MENU_INFO1, MENU_NAME_FIELD); 323 | break; 324 | } 325 | vpak->virtual_selected_row = -1; 326 | } 327 | 328 | if (current_menu == MENU_TPAK) 329 | { 330 | n64_virtualpak_write_string("TPAK SETTINGS", SUBHEADING + 2, MENU_NAME_FIELD); 331 | //A row has been selected 332 | uint32_t selected_row = vpak->virtual_selected_row; 333 | if (selected_row != -1) 334 | { 335 | //...which happens to be a ROM so change the default ROM 336 | uint32_t selected_rom = selected_row - (SUBHEADING + 2); 337 | if (selected_rom < num_roms) 338 | { 339 | strcpy(settings->default_tpak_rom[controller_page], 340 | gbrom_filenames[selected_rom]); 341 | n64_settings_update_checksum(settings); 342 | } 343 | } 344 | 345 | //Print a * next to the selected ROM. 346 | for (uint32_t i = 0; i < num_roms; i++) 347 | { 348 | n64_virtualpak_write_string(gbrom_titlenames[i], SUBHEADING + 2 + i, MENU_NAME_FIELD); 349 | //This ROM matches default! 350 | if (strcmp(gbrom_filenames[i], settings->default_tpak_rom[controller_page]) == 0) 351 | n64_virtualpak_write_string("****", SUBHEADING + 2 + i, MENU_EXT_FIELD); 352 | } 353 | vpak->virtual_selected_row = -1; 354 | } 355 | 356 | if (current_menu == MENU_CONTROLLER_SETTINGS) 357 | { 358 | sprintf(buff, "CONTROLLER %u", controller_page + 1); 359 | n64_virtualpak_write_string(buff, SUBHEADING + 0, MENU_NAME_FIELD); 360 | n64_virtualpak_write_string("________________", SUBHEADING + 1, MENU_NAME_FIELD); 361 | n64_virtualpak_write_string("CONT SETTINGS", SUBHEADING + 2, MENU_NAME_FIELD); 362 | 363 | n64_virtualpak_write_string("SENSITIVITY+", SUBHEADING + 4, MENU_NAME_FIELD); 364 | n64_virtualpak_write_string("SENSITIVITY-", SUBHEADING + 5, MENU_NAME_FIELD); 365 | 366 | n64_virtualpak_write_string("DEADZONE+", SUBHEADING + 7, MENU_NAME_FIELD); 367 | n64_virtualpak_write_string("DEADZONE-", SUBHEADING + 8, MENU_NAME_FIELD); 368 | 369 | n64_virtualpak_write_string("SNAP TOGGLE", SUBHEADING + 10, MENU_NAME_FIELD); 370 | n64_virtualpak_write_string("OCTA TOGGLE", SUBHEADING + 11, MENU_NAME_FIELD); 371 | 372 | n64_virtualpak_write_string("RESTORE DEFAULT", SUBHEADING + 12, MENU_NAME_FIELD); 373 | 374 | //A row has been selected, adjust settings accordingly 375 | uint32_t selected_row = vpak->virtual_selected_row; 376 | if (selected_row != -1) 377 | { 378 | if (selected_row == SUBHEADING + 4 && settings->sensitivity[controller_page] < 4) 379 | settings->sensitivity[controller_page]++; //Sensitivity increase 380 | else if (selected_row == SUBHEADING + 5 && settings->sensitivity[controller_page] > 0) 381 | settings->sensitivity[controller_page]--; //Sensitivity decrese 382 | else if (selected_row == SUBHEADING + 7 && settings->deadzone[controller_page] < 4) 383 | settings->deadzone[controller_page]++; //Deadzone increase 384 | else if (selected_row == SUBHEADING + 8 && settings->deadzone[controller_page] > 0) 385 | settings->deadzone[controller_page]--; //Deadzone decrease 386 | else if (selected_row == SUBHEADING + 10) 387 | settings->snap_axis[controller_page] ^= 1; //Toggle axis 45 degree snapping 388 | else if (selected_row == SUBHEADING + 11) 389 | settings->octa_correct[controller_page] ^= 1; //Toggle octagonal correction 390 | else if (selected_row == SUBHEADING + 12) 391 | { 392 | settings->deadzone[controller_page] = DEFAULT_DEADZONE; 393 | settings->sensitivity[controller_page] = DEFAULT_SENSITIVITY; 394 | settings->snap_axis[controller_page] = DEFAULT_SNAP; 395 | settings->octa_correct[controller_page] = DEFAULT_OCTA_CORRECT; 396 | } 397 | n64_settings_update_checksum(settings); 398 | } 399 | 400 | //Print the current values of each setting 401 | sprintf(buff, "%u\0", settings->sensitivity[controller_page]); 402 | n64_virtualpak_write_string(buff, SUBHEADING + 4, MENU_EXT_FIELD); 403 | 404 | sprintf(buff, "%u\0", settings->deadzone[controller_page]); 405 | n64_virtualpak_write_string(buff, SUBHEADING + 7, MENU_EXT_FIELD); 406 | 407 | sprintf(buff, "%u\0", settings->snap_axis[controller_page]); 408 | n64_virtualpak_write_string(buff, SUBHEADING + 10, MENU_EXT_FIELD); 409 | 410 | sprintf(buff, "%u\0", settings->octa_correct[controller_page]); 411 | n64_virtualpak_write_string(buff, SUBHEADING + 11, MENU_EXT_FIELD); 412 | 413 | vpak->virtual_selected_row = -1; 414 | } 415 | 416 | if (current_menu == MENU_INFO0 || current_menu == MENU_INFO1) 417 | { 418 | n64_virtualpak_write_string("INFO PAGE", SUBHEADING + 2, MENU_NAME_FIELD); 419 | char *msg = NULL; 420 | char line_text[17] = {0}; 421 | uint32_t pos = 0; 422 | uint32_t line = SUBHEADING + 3; 423 | 424 | if (current_menu == MENU_INFO0) 425 | msg = info_text_0; 426 | if (current_menu == MENU_INFO1) 427 | msg = info_text_1; 428 | 429 | while (*msg && msg != NULL && pos < sizeof(info_text_0)) 430 | { 431 | //On a line break or end of line print that line and prep to print on next line 432 | if ((*msg == '\n' || pos == 16) && line < 15) 433 | { 434 | n64_virtualpak_write_string(line_text, line, MENU_NAME_FIELD); 435 | memset(line_text, 0x00, sizeof(line_text)); 436 | line += 1; 437 | pos = 0; 438 | } 439 | else if (pos < 16) 440 | { 441 | line_text[pos++] = *msg; 442 | } 443 | msg++; 444 | } 445 | vpak->virtual_selected_row = -1; 446 | } 447 | 448 | vpak->virtual_update_req = 0; 449 | } 450 | 451 | void n64_virtualpak_write_info_1(char *msg) 452 | { 453 | strncpy(info_text_0, msg, sizeof(info_text_0)); 454 | } 455 | 456 | void n64_virtualpak_write_info_2(char *msg) 457 | { 458 | strncpy(info_text_1, msg, sizeof(info_text_1)); 459 | } 460 | 461 | uint8_t n64_virtualpak_get_controller_page() 462 | { 463 | return controller_page; 464 | } 465 | -------------------------------------------------------------------------------- /src/n64/n64_virtualpak.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #ifndef N64_VIRTUALPAK_H_ 5 | #define N64_VIRTUALPAK_H_ 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #define MENU_LINE1 0 12 | #define MENU_LINE2 1 13 | #define MENU_LINE3 2 14 | #define MENU_LINE4 3 15 | #define MENU_LINE5 4 16 | #define MENU_LINE6 5 17 | #define MENU_LINE7 6 18 | #define MENU_LINE8 7 19 | #define MENU_LINE9 8 20 | #define MENU_LINE10 9 21 | #define MENU_LINE11 10 22 | #define MENU_LINE12 11 23 | #define MENU_LINE13 12 24 | #define MENU_LINE14 13 25 | #define MENU_LINE15 14 26 | #define MENU_LINE16 15 27 | 28 | #define MENU_NAME_FIELD 0 29 | #define MENU_EXT_FIELD 1 30 | 31 | void n64_virtualpak_init(n64_mempack *vpak); 32 | void n64_virtualpak_update(n64_mempack *vpak); 33 | void n64_virtualpak_read32(uint16_t address, uint8_t *rx_buff); 34 | void n64_virtualpak_write32(uint16_t address, uint8_t *tx_buff); 35 | void n64_virtualpak_write_info_1(char* msg); 36 | void n64_virtualpak_write_info_2(char* msg); 37 | uint8_t n64_virtualpak_get_controller_page(void); 38 | 39 | #ifdef __cplusplus 40 | } 41 | #endif 42 | 43 | #endif /* N64_VIRTUALPAK_H_ */ 44 | -------------------------------------------------------------------------------- /src/n64_wrapper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include "n64_controller.h" 6 | #include "n64_wrapper.h" 7 | #include "memory.h" 8 | #include "usb64_conf.h" 9 | #include "fileio.h" 10 | 11 | /* 12 | * Function: Reads a hardware realtime clock and populates day,h,m,s. 13 | * Used by Pokemon Gameboy games only with TPAK that have a RTC. 14 | * ---------------------------- 15 | * Returns void 16 | * 17 | * day_high: Bit 0 Most significant bit of Day Counter (Bit 8) 18 | * Bit 6 Halt (0=Active, 1=Stop Timer) 19 | * Bit 7 Day Counter Carry Bit (1=Counter Overflow) 20 | * day_low: Lower 8 bits of Day Counter (0-255) 21 | * h: Pointer to an hour value (0-23) 22 | * m: Pointer to a minute value (0-59) 23 | * s: Pointer to a second value (0-59) 24 | */ 25 | void n64hal_rtc_read(uint8_t *day_high, uint8_t *day_low, uint8_t *h, uint8_t *m, uint8_t *s) 26 | { 27 | //Not implemented 28 | } 29 | 30 | /* 31 | * Function: Writes to a hardware realtime clock with day,h,m,s,dst 32 | * Used by Pokemon Gameboy games only with TPAK that have a RTC. 33 | * ---------------------------- 34 | * Returns void 35 | * 36 | * day_high: Bit 0 Most significant bit of Day Counter (Bit 8) 37 | * Bit 6 Halt (0=Active, 1=Stop Timer) 38 | * Bit 7 Day Counter Carry Bit (1=Counter Overflow) 39 | * day_low: Lower 8 bits of Day Counter (0-255) 40 | * h: Pointer to an hour value (0-23) 41 | * m: Pointer to a minute value (0-59) 42 | * s: Pointer to a second value (0-59) 43 | */ 44 | void n64hal_rtc_write(uint8_t *day_high, uint8_t *day_low, uint8_t *h, uint8_t *m, uint8_t *s) 45 | { 46 | //Not implemented 47 | } 48 | 49 | /* 50 | * Function: Enable a high speed clock (>20Mhz or so) which is used for low level accurate timings. 51 | * Timer range should be atleast 32-bit. 52 | * Not speed critical 53 | * ---------------------------- 54 | * Returns: Void 55 | */ 56 | void n64hal_hs_tick_init() 57 | { 58 | ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; 59 | } 60 | 61 | /* 62 | * Function: Returns to clock rate of the high speed clock in Hz. 63 | * Speed critical! 64 | * ---------------------------- 65 | * Returns: The rate of the high speed clock in Hz 66 | */ 67 | uint32_t n64hal_hs_tick_get_speed() 68 | { 69 | return F_CPU; 70 | } 71 | 72 | /* 73 | * Function: Get the current value of the high speed clock. 74 | * Speed critical! 75 | * ---------------------------- 76 | * Returns: A timer count that should run fairly fast (>20Mhz or so) 77 | */ 78 | uint32_t n64hal_hs_tick_get() 79 | { 80 | return ARM_DWT_CYCCNT; 81 | } 82 | 83 | /* 84 | * Function: Flips the gpio pin direction from an output (driven low) to an input (pulled up) 85 | * for the controller passed by controller. 86 | * Speed critical! 87 | * ---------------------------- 88 | * Returns: void 89 | * 90 | * controller: Pointer to the n64 controller struct which contains the gpio mapping 91 | * val: N64_OUTPUT or N64_INPUT 92 | */ 93 | void n64hal_input_swap(n64_input_dev_t *controller, uint8_t val) 94 | { 95 | switch (val) 96 | { 97 | case N64_OUTPUT: 98 | pinMode(controller->gpio_pin, OUTPUT); 99 | break; 100 | case N64_INPUT: 101 | default: 102 | pinMode(controller->gpio_pin, INPUT_PULLUP); 103 | break; 104 | } 105 | } 106 | 107 | /* 108 | * Function: Returns the data line level for the n64 controller passed to this function. 109 | * Speed critical! 110 | * ---------------------------- 111 | * Returns: 1 of the line if high, or 0 if the line is low. 112 | * 113 | * controller: Pointer to the n64 controller struct which contains the gpio mapping 114 | */ 115 | uint8_t n64hal_input_read(n64_input_dev_t *controller) 116 | { 117 | return digitalReadFast(controller->gpio_pin); 118 | } 119 | 120 | /* 121 | * Function: Sets an output GPI to a level 122 | * Speed critical! 123 | * ---------------------------- 124 | * Returns: void. 125 | * 126 | * pin: Arduino compatible pin number 127 | * level: 1 or 0 128 | */ 129 | void n64hal_output_set(uint8_t pin, uint8_t level) 130 | { 131 | digitalWriteFast(pin, level); 132 | } 133 | 134 | /* 135 | * Function: Returns an array of data read from external ram. 136 | * ---------------------------- 137 | * Returns: void 138 | * 139 | * rx_buff: The buffer the returned data will be stored 140 | * src: Pointer to the base address of the source data. 141 | * offset: Bytes from the base address the actual data is we need. 142 | * len: How many bytes to read. 143 | */ 144 | void n64hal_read_extram(void *rx_buff, void *src, uint32_t offset, uint32_t len) 145 | { 146 | memcpy(rx_buff, (void *)((uint32_t)src + offset), len); 147 | } 148 | 149 | /* 150 | * Function: Writes an array of data read to external ram. 151 | * ---------------------------- 152 | * Returns: void 153 | * 154 | * tx_buff: The buffer of data to write 155 | * dst: Pointer to the base address of the destination data. 156 | * offset: Bytes from the base address where we need to write. 157 | * len: How many bytes to write. 158 | */ 159 | void n64hal_write_extram(void *tx_buff, void *dst, uint32_t offset, uint32_t len) 160 | { 161 | memcpy((void *)((uint32_t)dst + offset), tx_buff, len); 162 | memory_mark_dirty(dst); 163 | } 164 | 165 | /* 166 | * Function: Returns a list of gameboy roms located on nonvolatile storage 167 | * WARNING: This mallocs memory on the heap. It must be free'd by user. 168 | * ---------------------------- 169 | * Returns: Number of roms found 170 | * 171 | * gb_list: A list of char pointers to populate 172 | * max: Max number of roms to return 173 | */ 174 | uint32_t n64hal_list_gb_roms(char **gb_list, uint32_t max) 175 | { 176 | //Retrieve full directory list 177 | char *file_list[256]; 178 | uint32_t num_files = fileio_list_directory(file_list, 256); 179 | 180 | //Find only files with .gb or gbc extensions to populate rom list. 181 | uint32_t rom_count = 0; 182 | for (uint32_t i = 0; i < num_files; i++) 183 | { 184 | if (file_list[i] == NULL) 185 | continue; 186 | 187 | if (strstr(file_list[i], ".GB\0") != NULL || strstr(file_list[i], ".GBC\0") != NULL || 188 | strstr(file_list[i], ".gb\0") != NULL || strstr(file_list[i], ".gbc\0") != NULL) 189 | { 190 | if (rom_count < max) 191 | { 192 | gb_list[rom_count] = (char *)malloc(strlen(file_list[i]) + 1); 193 | strcpy(gb_list[rom_count], file_list[i]); 194 | rom_count++; 195 | } 196 | } 197 | //Free file list as we go 198 | free(file_list[i]); 199 | } 200 | return rom_count; 201 | } 202 | 203 | /* 204 | * Function: Reads data from unbuffered memory. (i.e SD card) 205 | * ---------------------------- 206 | * Returns: Number of roms found 207 | * 208 | * name: Name of file 209 | * file_offset: Located to start reading file 210 | * data: buffer to put data in 211 | * len: number of bytes t0 read. 212 | */ 213 | void n64hal_read_storage(char *name, uint32_t file_offset, uint8_t *data, uint32_t len) 214 | { 215 | fileio_read_from_file(name, file_offset, data, len); 216 | } 217 | -------------------------------------------------------------------------------- /src/n64_wrapper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #ifndef N64_N64_WRAPPER_H_ 5 | #define N64_N64_WRAPPER_H_ 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #include 12 | #include "n64_controller.h" 13 | 14 | #define N64_OUTPUT 1 15 | #define N64_INPUT 2 16 | 17 | //RTC wrapper prototypes (For gameboy roms with RTC, i.e Pokemon games) 18 | void n64hal_rtc_read(uint8_t *day_high, uint8_t *day_low, uint8_t *h, uint8_t *m, uint8_t *s); 19 | void n64hal_rtc_write(uint8_t *day_high, uint8_t *day_low, uint8_t *h, uint8_t *m, uint8_t *s); 20 | 21 | //Timer wrappers 22 | uint32_t n64hal_hs_tick_get_speed(); 23 | void n64hal_hs_tick_init(); 24 | uint32_t n64hal_hs_tick_get(); 25 | 26 | //RAM access wrappers 27 | void n64hal_read_extram(void *rx_buff, void *src, uint32_t offset, uint32_t len); 28 | void n64hal_write_extram(void *tx_buff, void *dst, uint32_t offset, uint32_t len); 29 | 30 | //GPIO wrappers 31 | void n64hal_output_set(uint8_t pin, uint8_t level); 32 | void n64hal_input_swap(n64_input_dev_t *controller, uint8_t val); 33 | uint8_t n64hal_input_read(n64_input_dev_t *controller); 34 | 35 | //FileIO wrappers 36 | uint32_t n64hal_list_gb_roms(char **list, uint32_t max); 37 | void n64hal_read_storage(char *name, uint32_t file_offset, uint8_t *data, uint32_t len); 38 | 39 | #ifdef __cplusplus 40 | } 41 | #endif 42 | #endif /* N64_N64_WRAPPER_H_ */ 43 | -------------------------------------------------------------------------------- /src/tft/controller_icon.h: -------------------------------------------------------------------------------- 1 | // Generated by : ImageConverter 565 Online 2 | // Generated from : cont.png 3 | // Time generated : Sun, 04 Oct 20 06:40:57 +0200 (Server timezone: CET) 4 | // Image Size : 48x45 pixels 5 | // Memory usage : 4320 bytes 6 | 7 | #include 8 | 9 | const unsigned short controller_icon[2160] PROGMEM = { 10 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0010 (16) pixels 11 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0020 (32) pixels 12 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0030 (48) pixels 13 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0040 (64) pixels 14 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0050 (80) pixels 15 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0060 (96) pixels 16 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0070 (112) pixels 17 | 0x10A2, 0x2945, 0x9CF3, 0x9492, 0x9492, 0x9492, 0x9492, 0x9492, 0x9492, 0x9492, 0x9492, 0x9492, 0x8C71, 0x7BCF, 0x10A2, 0x10A2, // 0x0080 (128) pixels 18 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0090 (144) pixels 19 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x00A0 (160) pixels 20 | 0x2945, 0x8410, 0xE71C, 0xD6BA, 0xDEDB, 0xDEDB, 0xDEDB, 0xDEDB, 0xDEDB, 0xDEDB, 0xDEDB, 0xDEDB, 0xD6BA, 0xC638, 0x4A49, 0x10A2, // 0x00B0 (176) pixels 21 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x00C0 (192) pixels 22 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x2124, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x10A2, // 0x00D0 (208) pixels 23 | 0x8C51, 0xD6BA, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE59, 0xCE59, 0xCE79, 0xD69A, 0xD69A, 0xCE59, 0x2104, // 0x00E0 (224) pixels 24 | 0x18E3, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x00F0 (240) pixels 25 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xFFDF, 0xE71C, 0xE71C, 0xE71C, 0xE71C, 0xE71C, 0xE71C, 0xE73C, // 0x0100 (256) pixels 26 | 0xCE79, 0xC618, 0xD69A, 0xD69A, 0xBDD7, 0xBDD7, 0xBDD7, 0xBDD7, 0xBDF7, 0xAD55, 0xAD55, 0xC618, 0xD69A, 0xCE79, 0xAD55, 0xE71C, // 0x0110 (272) pixels 27 | 0xE71C, 0xE71C, 0xE71C, 0xE71C, 0xE71C, 0xE71C, 0xEF7D, 0x9492, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0120 (288) pixels 28 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x8C51, 0xF79E, 0xE73C, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, // 0x0130 (304) pixels 29 | 0xC638, 0xC618, 0xD69A, 0xCE79, 0xD69A, 0xD69A, 0xD69A, 0xD69A, 0xD69A, 0xD69A, 0xD69A, 0xD69A, 0xCE79, 0xCE79, 0xAD55, 0xCE79, // 0x0140 (320) pixels 30 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xDEDB, 0xEF5D, 0xFFFF, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0150 (336) pixels 31 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x9CD3, 0xCE59, 0xD69A, 0xCE79, 0xD69A, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, // 0x0160 (352) pixels 32 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE59, 0xCE79, // 0x0170 (368) pixels 33 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xCE79, 0xCE79, 0xD6BA, 0xBDF7, 0x2965, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0180 (384) pixels 34 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xBDF7, 0xD6BA, 0xCE79, 0xD69A, 0xCE59, 0xCE59, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, // 0x0190 (400) pixels 35 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, // 0x01A0 (416) pixels 36 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xC639, 0xC639, 0xCE59, 0xD69A, 0xCE79, 0xE73C, 0x3186, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x01B0 (432) pixels 37 | 0x10A2, 0x10A2, 0x10A2, 0x39C7, 0xC618, 0xD69A, 0xCE79, 0xC638, 0x8430, 0x8430, 0xBDD7, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, // 0x01C0 (448) pixels 38 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, // 0x01D0 (464) pixels 39 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE5A, 0xCE32, 0xCE10, 0xC633, 0xCE59, 0xCE79, 0xDEDB, 0x6B4D, 0x18E3, 0x10A2, 0x10A2, 0x10A2, // 0x01E0 (480) pixels 40 | 0x10A2, 0x10A2, 0x10A2, 0xFFDF, 0xD69A, 0xCE79, 0xCE59, 0xB596, 0x0020, 0x0841, 0x94B2, 0xCE59, 0xCE79, 0xCE79, 0xCE79, 0xCE79, // 0x01F0 (496) pixels 41 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, // 0x0200 (512) pixels 42 | 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xB5D9, 0xE6A4, 0xE680, 0xD628, 0xBDF9, 0xD69A, 0xCE79, 0xE73C, 0x7BCF, 0x10A2, 0x10A2, 0x10A2, // 0x0210 (528) pixels 43 | 0x10A2, 0x10A2, 0x10A2, 0xE73C, 0xD69A, 0xCE79, 0xD69A, 0xC618, 0x1082, 0x10A2, 0xA534, 0xD6BA, 0xD69A, 0xD69A, 0xCE79, 0xCE79, // 0x0220 (544) pixels 44 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, // 0x0230 (560) pixels 45 | 0xD69A, 0xD69A, 0xCE7A, 0xCE9C, 0xB5DC, 0xD644, 0xDE60, 0xD629, 0xBDFB, 0xCE9C, 0xD69A, 0xDEDB, 0x738E, 0x10A2, 0x10A2, 0x10A2, // 0x0240 (576) pixels 46 | 0x10A2, 0x10A2, 0x10A2, 0xEF5D, 0xCE59, 0x5AEB, 0x18C3, 0x2104, 0x2104, 0x2104, 0x2124, 0x1082, 0x2965, 0xCE79, 0xD69A, 0xCE79, // 0x0250 (592) pixels 47 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xC679, 0xBE79, 0xBE59, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xCE19, // 0x0260 (608) pixels 48 | 0xCE19, 0xCE5A, 0xD695, 0xEEC0, 0xDE41, 0xC615, 0xBDF9, 0xCE31, 0xEEC1, 0xDE40, 0xBDF7, 0xD6BB, 0x738E, 0x10A2, 0x10A2, 0x10A2, // 0x0270 (624) pixels 49 | 0x10A2, 0x10A2, 0x10A2, 0xEF5D, 0xCE59, 0x52AA, 0x1082, 0x18C3, 0x2124, 0x2124, 0x0861, 0x10A2, 0x2124, 0xCE79, 0xD69A, 0xCE79, // 0x0280 (640) pixels 50 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xC679, 0xD430, 0xD9C7, 0xB9C7, 0xC69A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE3A, 0x4525, // 0x0290 (656) pixels 51 | 0x3CA4, 0x7D4F, 0xD655, 0xE680, 0xDE60, 0xC5F6, 0xBDFA, 0xCE11, 0xE680, 0xDE60, 0xBDF7, 0xCE79, 0x6B4D, 0x10A2, 0x10A2, 0x10A2, // 0x02A0 (672) pixels 52 | 0x10A2, 0x10A2, 0x10A2, 0xEF5D, 0xCE79, 0x8C71, 0x6B4D, 0x632C, 0x18E3, 0x1082, 0x4A69, 0x5AEB, 0x73AE, 0xCE79, 0xCE79, 0xCE79, // 0x02B0 (688) pixels 53 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xC679, 0xCB4D, 0xD000, 0xB800, 0xBE9A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE1A, 0x0C60, // 0x02C0 (704) pixels 54 | 0x0400, 0x64EA, 0xD657, 0xD64B, 0xCE2C, 0xD64E, 0xCE2E, 0xCE2D, 0xCE2C, 0xCE2B, 0xC639, 0xCE59, 0x632C, 0x10A2, 0x10A2, 0x10A2, // 0x02D0 (720) pixels 55 | 0x10A2, 0x10A2, 0x10A2, 0xE71C, 0xD69A, 0xCE79, 0xCE79, 0xBDF7, 0x0861, 0x10A2, 0x9CD3, 0xD69A, 0xCE79, 0xD69A, 0xCE79, 0xCE79, // 0x02E0 (736) pixels 56 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xC679, 0xBC10, 0xB965, 0xB965, 0xC69A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE3A, 0x3C64, // 0x02F0 (752) pixels 57 | 0x3463, 0x64CE, 0x9D1A, 0x94F9, 0xBE1B, 0xE684, 0xE680, 0xD629, 0xBDFA, 0xBE19, 0xD69A, 0xCE79, 0x632C, 0x10A2, 0x10A2, 0x10A2, // 0x0300 (768) pixels 58 | 0x10A2, 0x10A2, 0x10A2, 0xD69A, 0xCE79, 0xD69A, 0xCE79, 0xAD75, 0x10A2, 0x10A2, 0x8C71, 0xCE79, 0xD69A, 0xCE79, 0xCE79, 0xCE79, // 0x0310 (784) pixels 59 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xC679, 0xBE79, 0xBE59, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xCE19, // 0x0320 (800) pixels 60 | 0xDE79, 0x63DB, 0x00BD, 0x0059, 0xD699, 0xD644, 0xDE60, 0xD628, 0xBDF9, 0xD69A, 0xD69A, 0xCE79, 0x632C, 0x10A2, 0x10A2, 0x10A2, // 0x0330 (816) pixels 61 | 0x10A2, 0x10A2, 0x10A2, 0xD6BA, 0xCE79, 0xD69A, 0xD69A, 0xD69A, 0xD69A, 0xD69A, 0xD69A, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, // 0x0340 (832) pixels 62 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, // 0x0350 (848) pixels 63 | 0xCE58, 0x5B98, 0x0079, 0x0059, 0xD677, 0xBDF9, 0xBDDA, 0xC61A, 0xD69A, 0xD69A, 0xD69A, 0xCE79, 0x632C, 0x10A2, 0x10A2, 0x10A2, // 0x0360 (864) pixels 64 | 0x10A2, 0x10A2, 0x10A2, 0xD6BA, 0xBDF7, 0xBDF7, 0xC638, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xD69A, 0xD69A, 0xCE79, 0xCE79, // 0x0370 (880) pixels 65 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, // 0x0380 (896) pixels 66 | 0xD699, 0xBDF8, 0xAD77, 0xA557, 0xD699, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xBDF7, 0xBDF7, 0xCE59, 0x632C, 0x10A2, 0x10A2, 0x10A2, // 0x0390 (912) pixels 67 | 0x10A2, 0x10A2, 0x10A2, 0xD6BA, 0xC638, 0xCE59, 0xC638, 0xC638, 0xC638, 0xC638, 0xC638, 0xCE59, 0xCE79, 0xCE79, 0xD69A, 0xCE79, // 0x03A0 (928) pixels 68 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xCE79, 0xCE59, 0xCE59, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, // 0x03B0 (944) pixels 69 | 0xD69A, 0xCE79, 0xCE58, 0xCE58, 0xC638, 0xC638, 0xC638, 0xC638, 0xC638, 0xCE59, 0xCE59, 0xCE79, 0x632C, 0x10A2, 0x10A2, 0x10A2, // 0x03C0 (960) pixels 70 | 0x10A2, 0x10A2, 0x10A2, 0xD6BA, 0xCE79, 0xD69A, 0xCE59, 0xC618, 0xC638, 0xBDF7, 0xBDF7, 0x9CD3, 0x73AE, 0x73AE, 0xD6BA, 0xCE79, // 0x03D0 (976) pixels 71 | 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xAD55, 0x8410, 0x8410, 0xD6BA, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xCE79, // 0x03E0 (992) pixels 72 | 0xD69A, 0xA534, 0x73AE, 0x73AE, 0xC638, 0xC618, 0xC618, 0xC618, 0xC638, 0xD69A, 0xD69A, 0xCE79, 0x632C, 0x10A2, 0x10A2, 0x10A2, // 0x03F0 (1008) pixels 73 | 0x10A2, 0x10A2, 0x10A2, 0xD6BA, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xD69A, 0xBDF7, 0xBDF7, 0x52AA, 0x10A2, 0x10A2, 0xC618, 0xAD75, // 0x0400 (1024) pixels 74 | 0xC618, 0xD69A, 0xCE79, 0xCE79, 0xD69A, 0xC618, 0x632C, 0x0861, 0x0861, 0xBDF7, 0xCE59, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xAD75, // 0x0410 (1040) pixels 75 | 0xBDD7, 0x52AA, 0x10A2, 0x10A2, 0xD6BA, 0xCE79, 0xD69A, 0xD69A, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0x632C, 0x10A2, 0x10A2, 0x10A2, // 0x0420 (1056) pixels 76 | 0x10A2, 0x10A2, 0x10A2, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xBDD7, 0xC618, 0xC618, 0x5AEB, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0430 (1072) pixels 77 | 0x7BCF, 0xDEFB, 0xD69A, 0xD69A, 0xDEDB, 0x4208, 0x0841, 0x0861, 0x0861, 0x0020, 0xBDD7, 0xD6BA, 0xD69A, 0xD69A, 0xE73C, 0x10A2, // 0x0440 (1088) pixels 78 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xBDF7, 0xCE59, 0x6B4D, 0x10A2, 0x10A2, 0x10A2, // 0x0450 (1104) pixels 79 | 0x10A2, 0x10A2, 0x10A2, 0xEF5D, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xBDF7, 0xB596, 0xBDF7, 0x5AEB, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0460 (1120) pixels 80 | 0x10A2, 0x39C7, 0xCE79, 0xCE79, 0x18C3, 0x10A2, 0x8430, 0xFFDF, 0xE71C, 0x1082, 0x10A2, 0x738E, 0xCE59, 0xB596, 0x10A2, 0x10A2, // 0x0470 (1136) pixels 81 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xEF5D, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xBDF7, 0xBDF7, 0x5AEB, 0x10A2, 0x10A2, 0x10A2, // 0x0480 (1152) pixels 82 | 0x10A2, 0x10A2, 0x10A2, 0xE73C, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xBDF7, 0xB596, 0xC638, 0x630C, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0490 (1168) pixels 83 | 0x10A2, 0x39E7, 0xCE79, 0xCE79, 0x10A2, 0x10A2, 0x7BEF, 0xEF7D, 0xEF5D, 0x10A2, 0x10A2, 0x738E, 0xCE59, 0xB5B6, 0x10A2, 0x10A2, // 0x04A0 (1184) pixels 84 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xF7BE, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xBDF7, 0xBDF7, 0x5AEB, 0x10A2, 0x10A2, 0x10A2, // 0x04B0 (1200) pixels 85 | 0x10A2, 0x10A2, 0x10A2, 0xE73C, 0xCE79, 0xCE79, 0xCE79, 0xC638, 0xC618, 0xBDD7, 0x738E, 0x2945, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x04C0 (1216) pixels 86 | 0x10A2, 0x4208, 0xD6BA, 0xD69A, 0x73AE, 0x2965, 0x4228, 0x7BEF, 0x73AE, 0x10A2, 0x528A, 0xA514, 0xCE79, 0xBDD7, 0x10A2, 0x10A2, // 0x04D0 (1232) pixels 87 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x6B4D, 0xC618, 0xD69A, 0xCE79, 0xCE79, 0xC638, 0xC618, 0xBDF7, 0x630C, 0x10A2, 0x10A2, 0x10A2, // 0x04E0 (1248) pixels 88 | 0x10A2, 0x10A2, 0x10A2, 0xEF5D, 0xCE79, 0xCE79, 0xCE59, 0xBDF7, 0xBDF7, 0xC618, 0x2104, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x04F0 (1264) pixels 89 | 0x10A2, 0x3186, 0xA514, 0xDEDB, 0xD6BA, 0x6B6D, 0x2945, 0x0841, 0x10A2, 0x2965, 0xBDD7, 0xD69A, 0xBDF7, 0x8430, 0x10A2, 0x10A2, // 0x0500 (1280) pixels 90 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xB596, 0xD6BA, 0xCE79, 0xCE79, 0xBDF7, 0xBDF7, 0xBDF7, 0x630C, 0x10A2, 0x10A2, 0x10A2, // 0x0510 (1296) pixels 91 | 0x10A2, 0x10A2, 0x10A2, 0xEF7D, 0xD69A, 0xD69A, 0xCE59, 0xBDF7, 0xAD75, 0xC618, 0x2945, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0520 (1312) pixels 92 | 0x10A2, 0x10A2, 0x10A2, 0xE73C, 0xD69A, 0xD6BA, 0x6B6D, 0x10A2, 0x10A2, 0xE71C, 0xD69A, 0xCE79, 0x7BCF, 0x10A2, 0x10A2, 0x10A2, // 0x0530 (1328) pixels 93 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xB5B6, 0xD6BA, 0xCE79, 0xCE79, 0xBDF7, 0xB596, 0xBDF7, 0x632C, 0x10A2, 0x10A2, 0x10A2, // 0x0540 (1344) pixels 94 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xBDD7, 0xCE59, 0xBDF7, 0xBDF7, 0xB596, 0xD69A, 0x3186, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0550 (1360) pixels 95 | 0x10A2, 0x10A2, 0x0841, 0xE73C, 0xCE79, 0xD69A, 0xD69A, 0xD69A, 0xCE79, 0xD69A, 0xD69A, 0xCE79, 0x7BEF, 0x10A2, 0x10A2, 0x10A2, // 0x0560 (1376) pixels 96 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xC638, 0xDEFB, 0xCE59, 0xBDF7, 0xC618, 0xC618, 0x2104, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0570 (1392) pixels 97 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xAD75, 0xC638, 0xBDD7, 0xB596, 0xC618, 0x0841, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0580 (1408) pixels 98 | 0x10A2, 0x10A2, 0x0841, 0xE73C, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xCE79, 0x7BEF, 0x10A2, 0x10A2, 0x10A2, // 0x0590 (1424) pixels 99 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x7BCF, 0xCE59, 0xBDF7, 0xB596, 0xC618, 0x2965, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x05A0 (1440) pixels 100 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xB5B6, 0xC618, 0xB5B6, 0xB596, 0xCE79, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x05B0 (1456) pixels 101 | 0x10A2, 0x10A2, 0x0861, 0xEF7D, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xD6BA, 0x8430, 0x10A2, 0x10A2, 0x10A2, // 0x05C0 (1472) pixels 102 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x73AE, 0xCE79, 0xB5B6, 0xB596, 0xCE59, 0x3186, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x05D0 (1488) pixels 103 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x632C, 0x9CF3, 0xBDD7, 0xB596, 0x738E, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x05E0 (1504) pixels 104 | 0x10A2, 0x10A2, 0x0020, 0x8410, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0xCE79, 0x9CD3, 0x4A49, 0x10A2, 0x10A2, 0x10A2, // 0x05F0 (1520) pixels 105 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x4208, 0x8C71, 0xBDD7, 0xBDF7, 0x73AE, 0x18E3, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0600 (1536) pixels 106 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x5AEB, 0xA534, 0x9492, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0610 (1552) pixels 107 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xD69A, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xCE79, 0x4208, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0620 (1568) pixels 108 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x2945, 0xAD55, 0xAD75, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0630 (1584) pixels 109 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0640 (1600) pixels 110 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xD6BA, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xCE79, 0x4A49, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0650 (1616) pixels 111 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0660 (1632) pixels 112 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0670 (1648) pixels 113 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xD6BA, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xCE79, 0x4A49, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0680 (1664) pixels 114 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0690 (1680) pixels 115 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x06A0 (1696) pixels 116 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xD6BA, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xCE79, 0x4A49, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x06B0 (1712) pixels 117 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x06C0 (1728) pixels 118 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x06D0 (1744) pixels 119 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xD6BA, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xCE79, 0x4A49, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x06E0 (1760) pixels 120 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x06F0 (1776) pixels 121 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0700 (1792) pixels 122 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xD6BA, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xD69A, 0x4A49, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0710 (1808) pixels 123 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0720 (1824) pixels 124 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0730 (1840) pixels 125 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0xD69A, 0xD69A, 0xCE79, 0xCE79, 0xCE79, 0xD69A, 0xCE79, 0x4A49, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0740 (1856) pixels 126 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0750 (1872) pixels 127 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0760 (1888) pixels 128 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x94B2, 0xD6BA, 0xCE79, 0xCE79, 0xCE79, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0770 (1904) pixels 129 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0780 (1920) pixels 130 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0790 (1936) pixels 131 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x9CD3, 0xD6BA, 0xD69A, 0xD69A, 0xE71C, 0x18C3, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x07A0 (1952) pixels 132 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x07B0 (1968) pixels 133 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x07C0 (1984) pixels 134 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x2124, 0x8410, 0xD6BA, 0xDEDB, 0x3186, 0x0020, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x07D0 (2000) pixels 135 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x07E0 (2016) pixels 136 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x07F0 (2032) pixels 137 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x2965, 0x73AE, 0x73AE, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0800 (2048) pixels 138 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0810 (2064) pixels 139 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0820 (2080) pixels 140 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0830 (2096) pixels 141 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0840 (2112) pixels 142 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0850 (2128) pixels 143 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0860 (2144) pixels 144 | 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, 0x10A2, // 0x0870 (2160) pixels 145 | }; 146 | -------------------------------------------------------------------------------- /src/tft/tft.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include 6 | #include "USBHost_t36.h" 7 | #include "usb64_conf.h" 8 | #include "n64_controller.h" 9 | #include "input.h" 10 | #include "memory.h" 11 | #include "printf.h" 12 | #include "ILI9341_t3n.h" 13 | #include "tft.h" 14 | #include "fileio.h" 15 | 16 | #if (ENABLE_TFT_DISPLAY >= 1) 17 | #include "controller_icon.h" 18 | #include "usb64_logo.h" 19 | #include "ili9341_t3n_font_Arial.h" 20 | ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCK, TFT_MISO); 21 | DMAMEM uint16_t _framebuffer[320 * 240]; 22 | 23 | static uint8_t _tft_page = 0; 24 | static uint8_t _tft_max_pages = 2; 25 | static uint8_t _tft_update_needed = 0; 26 | 27 | static const uint8_t _tft_log_max_lines = 15; 28 | static char *_tft_log_text_lines[_tft_log_max_lines]; 29 | 30 | extern n64_input_dev_t n64_in_dev[MAX_CONTROLLERS]; 31 | extern n64_transferpak n64_tpak[MAX_CONTROLLERS]; 32 | 33 | extern float tempmonGetTemp(void); 34 | 35 | static const char *n64_peri_to_string(n64_input_dev_t *c) 36 | { 37 | static char text_buff[32]; 38 | 39 | //Handle some specific cases 40 | if (input_is_connected(c->id) == 0) 41 | { 42 | tft.setTextColor(ILI9341_WHITE); 43 | return "NOT CONNECTED"; 44 | } 45 | 46 | if (c->type == N64_MOUSE) 47 | { 48 | tft.setTextColor(ILI9341_GREEN); 49 | return "N64 MOUSE"; 50 | } 51 | 52 | if (c->type == N64_RANDNET) 53 | { 54 | tft.setTextColor(ILI9341_GREEN); 55 | return "N64 RANDNET"; 56 | } 57 | 58 | if (input_is_dualstick_mode(c->id)) 59 | { 60 | tft.setTextColor(ILI9341_GREEN); 61 | return "DUAL STICK MODE"; 62 | } 63 | 64 | switch (c->current_peripheral) 65 | { 66 | case PERI_NONE: 67 | tft.setTextColor(ILI9341_WHITE); 68 | return "NO PERIPHERAL"; 69 | 70 | case PERI_RUMBLE: 71 | tft.setTextColor(ILI9341_GREEN); 72 | return "RUMBLE PAK"; 73 | 74 | case PERI_MEMPAK: 75 | tft.setTextColor(ILI9341_GREEN); 76 | if (c->mempack->virtual_is_active) 77 | return "VIRTUAL PAK"; 78 | 79 | snprintf(text_buff, sizeof(text_buff), "MPAK (BANK %u)", c->mempack->id); 80 | return text_buff; 81 | 82 | case PERI_TPAK: 83 | tft.setTextColor(ILI9341_GREEN); 84 | snprintf(text_buff, sizeof(text_buff), "TPAK (%s)", (c->tpak->gbcart->rom == NULL) ? "NO ROM" : c->tpak->gbcart->title); 85 | return text_buff; 86 | 87 | default: 88 | tft.setTextColor(ILI9341_RED); 89 | return "UNKNOWN"; 90 | } 91 | } 92 | 93 | static void write_controller_status(int controller, int line, const char *text) 94 | { 95 | const int x_margin = 50; 96 | const int y_margin = 30; 97 | const int text_height = 14; 98 | const int line_padding = 2; 99 | const int controller_padding = 22; 100 | 101 | //Clear old text 102 | tft.fillRect(x_margin, y_margin + text_height + controller_padding * controller + (controller * 2 + line) * text_height + line_padding * line, 103 | tft.width() - x_margin, 104 | text_height + 2, BG_COLOUR); 105 | 106 | //Draw new text 107 | tft.setCursor(x_margin, y_margin + text_height + controller_padding * controller + 108 | (controller * 2 + line) * text_height + line_padding * line); 109 | 110 | tft.print(text); 111 | } 112 | #endif 113 | 114 | void tft_init() 115 | { 116 | #if (ENABLE_TFT_DISPLAY >= 1) 117 | tft.begin(); 118 | tft.setRotation(TFT_ROTATION); 119 | tft.setFrameBuffer(_framebuffer); 120 | tft.useFrameBuffer(true); 121 | 122 | tft_force_update(); 123 | #endif 124 | } 125 | 126 | void tft_try_update() 127 | { 128 | #if (ENABLE_TFT_DISPLAY >= 1) 129 | 130 | #if (0) 131 | //Dump the framebuffer to a file on the SD Card, 10 seconds after power up. 132 | //Convert to png with 133 | //ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt rgb565le -s 320x240 -i tft_dump.bin -f image2 -vcodec png tft_dump.png 134 | if (millis() > 10000) 135 | { 136 | fileio_write_to_file("tft_dump.bin", (uint8_t *)_framebuffer, sizeof(_framebuffer)); 137 | while (1); 138 | } 139 | #endif 140 | 141 | if (_tft_update_needed == 0) 142 | return; 143 | 144 | if (tft.asyncUpdateActive()) 145 | return; 146 | 147 | tft_force_update(); 148 | #endif 149 | } 150 | 151 | uint8_t tft_change_page(uint8_t page) 152 | { 153 | #if (ENABLE_TFT_DISPLAY >= 1) 154 | (page >= _tft_max_pages) ? _tft_page = 0 : _tft_page = page; 155 | return _tft_page; 156 | #else 157 | return 0; 158 | #endif 159 | } 160 | 161 | void tft_force_update() 162 | { 163 | #if (ENABLE_TFT_DISPLAY >= 1) 164 | static int8_t current_page = -1; 165 | char text_buff[64]; 166 | uint8_t page_changed = 0; 167 | 168 | while (tft.asyncUpdateActive()); 169 | 170 | if (_tft_page != current_page) 171 | { 172 | page_changed = 1; 173 | current_page = _tft_page; 174 | } 175 | 176 | /* Draw static elements for page */ 177 | if (page_changed) 178 | { 179 | tft.fillScreen(BG_COLOUR); 180 | 181 | //Draw usb64 logo 182 | tft.writeRect(0, 0, 120, 35, (uint16_t *)usb64_logo); 183 | 184 | tft.setFont(Arial_8); 185 | 186 | //Write the detected external ram 187 | tft.setTextColor(ILI9341_WHITE); 188 | snprintf(text_buff, sizeof(text_buff), "Detected RAM: %uMB", memory_get_ext_ram_size()); 189 | tft.setCursor(125, 0); 190 | tft.print(text_buff); 191 | 192 | //Write the detected SD card size 193 | uint32_t sd_size = SD.totalSize() / 1024 / 1024; 194 | if (sd_size == 0) 195 | tft.setTextColor(ILI9341_RED); 196 | snprintf(text_buff, sizeof(text_buff), "SD: %uMiB", sd_size); 197 | tft.setCursor(125, 10); 198 | tft.print(text_buff); 199 | 200 | //Write the current page number 201 | tft.setTextColor(ILI9341_WHITE); 202 | snprintf(text_buff, sizeof(text_buff), "%u/%u", _tft_page + 1, _tft_max_pages); 203 | tft.setCursor(tft.width() - 10 * 6, 10); 204 | tft.print(text_buff); 205 | 206 | tft.setTextColor(ILI9341_WHITE); 207 | 208 | switch (_tft_page) 209 | { 210 | case 0: 211 | //Draw the four controller images 212 | tft.writeRect(0, 40, 48, 45, (uint16_t *)controller_icon); 213 | tft.writeRect(0, 90, 48, 45, (uint16_t *)controller_icon); 214 | tft.writeRect(0, 140, 48, 45, (uint16_t *)controller_icon); 215 | tft.writeRect(0, 190, 48, 45, (uint16_t *)controller_icon); 216 | break; 217 | case 1: 218 | break; 219 | } 220 | } 221 | 222 | /* Draw dynamic elements for each page */ 223 | //Draw N64 sense status 224 | tft.setFont(Arial_8); 225 | tft.setCursor(tft.width() - 10 * 6, 0); 226 | tft.fillRect(tft.getCursorX(), tft.getCursorY(), tft.width() - tft.getCursorX(), 8, BG_COLOUR); 227 | if (digitalRead(N64_CONSOLE_SENSE) == 0) 228 | { 229 | tft.setTextColor(ILI9341_RED); 230 | tft.print("N64 is OFF"); 231 | } 232 | else 233 | { 234 | tft.setTextColor(ILI9341_GREEN); 235 | tft.print("N64 is ON"); 236 | } 237 | 238 | //Write Teensy temperature 239 | tft.setTextColor(ILI9341_WHITE); 240 | snprintf(text_buff, sizeof(text_buff), "%i°C", (int32_t)tempmonGetTemp()); 241 | tft.setCursor(125, 20); 242 | tft.fillRect(tft.getCursorX(), tft.getCursorY(), tft.width() - tft.getCursorX(), 8, BG_COLOUR); 243 | tft.print(text_buff); 244 | 245 | //These elements are present on specific pages only. 246 | switch (_tft_page) 247 | { 248 | case 0: 249 | //Print controller status screen 250 | tft.setFont(Arial_13); 251 | for (uint32_t i = 0; i < MAX_CONTROLLERS; i++) 252 | { 253 | snprintf(text_buff, sizeof(text_buff), "0x%04x/0x%04x\n", 254 | input_get_id_vendor(i), 255 | input_get_id_product(i)); 256 | 257 | write_controller_status(i, 0, n64_peri_to_string(&n64_in_dev[i])); 258 | write_controller_status(i, 1, text_buff); 259 | } 260 | break; 261 | case 1: 262 | //Print the debug log screen 263 | tft.fillRect(0, 40, tft.width(), tft.height() - 40, BG_COLOUR); 264 | tft.setTextColor(ILI9341_WHITE); 265 | tft.setCursor(0, 40); 266 | for (uint32_t i = 0; i < _tft_log_max_lines; i++) 267 | { 268 | if (_tft_log_text_lines[i] == NULL) 269 | break; 270 | 271 | tft.print(_tft_log_text_lines[i]); 272 | } 273 | 274 | break; 275 | } 276 | tft.updateScreenAsync(); 277 | _tft_update_needed = 0; 278 | #endif 279 | } 280 | 281 | void tft_flag_update() 282 | { 283 | #if (ENABLE_TFT_DISPLAY >= 1) 284 | _tft_update_needed = 1; 285 | #endif 286 | } 287 | 288 | void tft_add_log(char c) 289 | { 290 | #if (ENABLE_TFT_DISPLAY >= 1) 291 | static int tft_log_line_num = 0; 292 | static uint32_t tft_log_pos = 0; 293 | static char tft_log[256] = {0}; 294 | 295 | //Add character to tft log screen 296 | tft_log[tft_log_pos] = c; 297 | tft_log_pos++; 298 | 299 | //Build a new line to display 300 | if (c == '\n') 301 | { 302 | tft_log[tft_log_pos] = '\0'; 303 | _tft_log_text_lines[tft_log_line_num] = (char *)malloc(strlen(tft_log) + 1); 304 | strcpy(_tft_log_text_lines[tft_log_line_num], tft_log); 305 | tft_log_line_num++; 306 | tft_log_pos = 0; 307 | tft_flag_update(); 308 | } 309 | 310 | //Exceeded max lines, remove oldest line and shift lines up by one 311 | if (tft_log_line_num >= _tft_log_max_lines) 312 | { 313 | free(_tft_log_text_lines[0]); 314 | for (uint32_t i = 0; i < _tft_log_max_lines - 1; i++) 315 | { 316 | _tft_log_text_lines[i] = _tft_log_text_lines[i + 1]; 317 | } 318 | _tft_log_text_lines[_tft_log_max_lines - 1] = NULL; 319 | tft_log_line_num--; 320 | } 321 | #endif 322 | } -------------------------------------------------------------------------------- /src/tft/tft.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #ifndef _TFT_H 5 | #define _TFT_H 6 | 7 | #include 8 | #include "ILI9341_t3n.h" 9 | 10 | #define CENTER ILI9341_t3n::CENTER 11 | #define BG_COLOUR 0x10A2 12 | 13 | void tft_init(); 14 | uint8_t tft_change_page(uint8_t page); 15 | void tft_force_update(); //Will block until previous DMA complete. 16 | void tft_try_update(); //Will return if DMA busy, or framebuffer hasnt changed 17 | void tft_flag_update(); //Mark the framebuffer as dirty, draw at next update call 18 | void tft_add_log(char c); 19 | 20 | #endif -------------------------------------------------------------------------------- /src/usb64_conf.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Ryan Wendland, usb64 2 | // SPDX-License-Identifier: MIT 3 | 4 | #ifndef _USB64_CONF_h 5 | #define _USB64_CONF_h 6 | 7 | /* DEBUGGING OUTPUT - WARNING SOME OF THESE MAY BREAK TIMING AND CAUSE ISSUES 8 | USE ONLY FOR DEBUGGING */ 9 | #define serial_port Serial1 10 | #define DEBUG_STATUS 1 //General information 11 | #define DEBUG_N64 0 //For debugging N64 low level info 12 | #define DEBUG_TPAK 0 //For debugging N64 TPAK low level info. It's complex so has its own flag 13 | #define DEBUG_USBHOST 0 //For debugging the USB Host Stack 14 | #define DEBUG_FATFS 0 //For debugging the FATFS io 15 | #define DEBUG_MEMORY 0 //For debugging the memory allocator in external RAM. 16 | #define DEBUG_ERROR 1 //For showing critical errors 17 | 18 | /* USB HOST STACK */ 19 | #define ENABLE_USB_HUB 1 20 | 21 | /* N64 LIB */ 22 | #define MAX_CONTROLLERS 4 //Max is 4 23 | #define MAX_MICE 4 //0 to disable N64 mouse support. Must be <= MAX_CONTROLLERS 24 | #define MAX_KB 4 //0 to disable N64 randnet keyboard support. Must be <= MAX_CONTROLLERS 25 | #define MAX_GBROMS 10 //ROMS over this will just get ignored 26 | #define ENABLE_I2C_CONTROLLERS 0 //Received button presses over I2C, useful for integrating with a rasp pi etc. 27 | #define ENABLE_HARDWIRED_CONTROLLER 1 //Ability to hardware a N64 controller into the usb64. 28 | #define PERI_CHANGE_TIME 750 //Milliseconds to simulate a peripheral changing time. Needed for some games. 29 | 30 | /* PIN MAPPING */ 31 | #define N64_CONSOLE_SENSE 37 32 | #define N64_CONTROLLER_1_PIN 36 33 | #define N64_CONTROLLER_2_PIN 35 34 | #define N64_CONTROLLER_3_PIN 34 35 | #define N64_CONTROLLER_4_PIN 33 36 | #define N64_FRAME 23 //Pulses high each time N64 console requests input. Presumably related to frames. 37 | #define USER_LED_PIN 13 38 | 39 | //Hardwired interface. All digital inputs are pulled up, active low. 40 | #define HW_A 2 41 | #define HW_B 3 42 | #define HW_CU 4 43 | #define HW_CD 5 44 | #define HW_CL 6 45 | #define HW_CR 7 46 | #define HW_DU 8 47 | #define HW_DD 9 48 | #define HW_DL 10 49 | #define HW_DR 11 50 | #define HW_START 12 51 | #define HW_Z 28 52 | #define HW_R 29 53 | #define HW_L 30 54 | #define HW_RUMBLE 31 //Output, 1 when should be rumbling 55 | #define HW_EN 32 //Active low, pulled high 56 | #define HW_X 24 //Analog input, 0V to VCC. VCC/2 centre 57 | #define HW_Y 25 //Analog input, 0V to VCC. VCC/2 centre 58 | 59 | /* FILESYSTEM */ 60 | #define MAX_FILENAME_LEN 256 61 | #define SETTINGS_FILENAME "SETTINGS.DAT" 62 | #define GAMEBOY_SAVE_EXT ".SAV" //ROMFILENAME.SAV 63 | #define MEMPAK_SAVE_EXT ".MPK" //MEMPAKXX.MPK 64 | 65 | /* FIRMWARE DEFAULTS (CONFIGURABLE DURING USE) */ 66 | #define DEFAULT_SENSITIVITY 2 //0 to 4 (0 = low sensitivity, 4 = max) 67 | #define DEFAULT_DEADZONE 2 //0 to 4 (0 = no deadzone correction, 4 = max (40%)) 68 | #define DEFAULT_SNAP 1 //0 or 1 (0 = will output raw analog stick angle, 1 will snap to 45deg angles) 69 | #define DEFAULT_OCTA_CORRECT 1 //0 or 1 (Will correct the circular analog stuck shape to N64 octagonal) 70 | 71 | /* FIRMWARE DEFAULTS (NOT CONFIGURABLE DURING USE) */ 72 | #define SNAP_RANGE 5 //+/- what angle range will snap. 5 will snap to 45 degree if between 40 and 50 degrees. 73 | #define MOUSE_SENSITIVITY 2.0f //Just what felt right to me with my mouse. 74 | #define MAG_AT_45DEG 1.1f //Octagonal shape has a larger magnitude at the 45degree points. 1.1 times larger seems about right 75 | 76 | /* TFT DISPLAY */ 77 | #define ENABLE_TFT_DISPLAY 1 78 | #define TFT_ROTATION 1 //0-3 79 | #define TFT_DC 40 80 | #define TFT_CS 41 81 | #define TFT_MOSI 26 82 | #define TFT_SCK 27 83 | #define TFT_MISO 39 84 | #define TFT_RST 255 85 | 86 | 87 | /* DEBUG PRINTERS */ 88 | #include "printf.h" 89 | #define debug_print_status(fmt, ...) do { if (DEBUG_STATUS) printf(fmt, ##__VA_ARGS__); } while (0) 90 | #define debug_print_n64(fmt, ...) do { if (DEBUG_N64) printf(fmt, ##__VA_ARGS__); } while (0) 91 | #define debug_print_tpak(fmt, ...) do { if (DEBUG_TPAK) printf(fmt, ##__VA_ARGS__); } while (0) 92 | #define debug_print_usbhost(fmt, ...) do { if (DEBUG_USBHOST) printf(fmt, ##__VA_ARGS__); } while (0) 93 | #define debug_print_fatfs(fmt, ...) do { if (DEBUG_FATFS) printf(fmt, ##__VA_ARGS__); } while (0) 94 | #define debug_print_memory(fmt, ...) do { if (DEBUG_MEMORY) printf(fmt, ##__VA_ARGS__); } while (0) 95 | #define debug_print_error(fmt, ...) do { if (DEBUG_ERROR) printf(fmt, ##__VA_ARGS__); } while (0) 96 | #endif 97 | --------------------------------------------------------------------------------