From bd641b64762e55cfcf3bf06b1d3ca6e446471cd2 Mon Sep 17 00:00:00 2001 From: Roger Braunstein Date: Sat, 6 Jul 2024 17:39:20 -0700 Subject: [PATCH 1/5] Cherry-picking firmware changes only for Lynx support Updating header comments Better blocksize detection, cleanups --- Cart_Reader/Cart_Reader.ino | 37 +- Cart_Reader/Config.h | 7 + Cart_Reader/LYNX.ino | 263 ++++++++ Cart_Reader/OSCR.h | 6 + sd/lynx.txt | 1140 +++++++++++++++++++++++++++++++++++ 5 files changed, 1442 insertions(+), 11 deletions(-) create mode 100644 Cart_Reader/LYNX.ino create mode 100644 sd/lynx.txt diff --git a/Cart_Reader/Cart_Reader.ino b/Cart_Reader/Cart_Reader.ino index f5cd39c..3b56d48 100644 --- a/Cart_Reader/Cart_Reader.ino +++ b/Cart_Reader/Cart_Reader.ino @@ -183,7 +183,7 @@ template int EEPROM_readAnything(int ee, T& value) { #define rotate_to_change_STR 15 #define press_to_select_STR 16 -// This arrays holds the most often uses strings +// This array holds the most often used strings constexpr char string_press_button0[] PROGMEM = "Press Button..."; constexpr char string_sd_error1[] PROGMEM = "SD Error"; constexpr char string_did_not_verify3[] PROGMEM = "did not verify"; @@ -288,7 +288,7 @@ boolean root = 0; boolean filebrowse = 0; // Common -// 21 chars for SNES ROM name, one char for termination +// 21 chars for ROM name, one char for termination char romName[22]; unsigned long sramSize = 0; int romType = 0; @@ -456,7 +456,7 @@ uint32_t calculateCRC(char* fileName, char* folder, unsigned long offset) { /****************************************** CRC Functions for Atari, Fairchild, Ody2, Arc, etc. modules *****************************************/ -#if (defined(ENABLE_ODY2) || defined(ENABLE_ARC) || defined(ENABLE_FAIRCHILD) || defined(ENABLE_MSX) || defined(ENABLE_POKE) || defined(ENABLE_2600) || defined(ENABLE_5200) || defined(ENABLE_7800) || defined(ENABLE_C64) || defined(ENABLE_VECTREX) || defined(ENABLE_NES)) +#if (defined(ENABLE_ODY2) || defined(ENABLE_ARC) || defined(ENABLE_FAIRCHILD) || defined(ENABLE_MSX) || defined(ENABLE_POKE) || defined(ENABLE_2600) || defined(ENABLE_5200) || defined(ENABLE_7800) || defined(ENABLE_C64) || defined(ENABLE_VECTREX) || defined(ENABLE_NES) || defined(ENABLE_LYNX)) void printCRC(char* checkFile, uint32_t* crcCopy, unsigned long offset) { uint32_t crc = calculateCRC(checkFile, folder, offset); @@ -1091,10 +1091,11 @@ constexpr char modeItem22[] PROGMEM = "Casio Loopy"; constexpr char modeItem23[] PROGMEM = "Commodore 64"; constexpr char modeItem24[] PROGMEM = "Atari 5200"; constexpr char modeItem25[] PROGMEM = "Atari 7800"; -constexpr char modeItem26[] PROGMEM = "Vectrex"; -constexpr char modeItem27[] PROGMEM = "Flashrom Programmer"; -constexpr char modeItem28[] PROGMEM = "Self Test (3V)"; -constexpr char modeItem29[] PROGMEM = "About"; +constexpr char modeItem26[] PROGMEM = "Atari Lynx"; +constexpr char modeItem27[] PROGMEM = "Vectrex"; +constexpr char modeItem28[] PROGMEM = "Flashrom Programmer"; +constexpr char modeItem29[] PROGMEM = "Self Test (3V)"; +constexpr char modeItem30[] PROGMEM = "About"; static const char* const modeOptions[] PROGMEM = { #ifdef ENABLE_GBX @@ -1172,16 +1173,20 @@ static const char* const modeOptions[] PROGMEM = { #ifdef ENABLE_7800 modeItem25, #endif -#ifdef ENABLE_VECTREX +#ifdef ENABLE_LYNX modeItem26, #endif -#ifdef ENABLE_FLASH +#ifdef ENABLE_VECTREX modeItem27, #endif -#ifdef ENABLE_SELFTEST +#ifdef ENABLE_FLASH modeItem28, #endif - modeItem29, FSTRING_RESET +#ifdef ENABLE_SELFTEST + modeItem29, +#endif + modeItem30, + FSTRING_RESET }; uint8_t pageMenu(const __FlashStringHelper* question, const char* const* menuStrings, uint8_t entryCount, uint8_t default_choice = 0) { @@ -1385,6 +1390,13 @@ void mainMenu() { break; #endif +#ifdef ENABLE_LYNX + case SYSTEM_MENU_LYNX: + setup_LYNX(); + return lynxMenu(); + break; +#endif + #ifdef ENABLE_VECTREX case SYSTEM_MENU_VECTREX: setup_VECTREX(); @@ -3583,6 +3595,9 @@ void loop() { #ifdef ENABLE_7800 case CORE_7800: return a7800Menu(); #endif +#ifdef ENABLE_LYNX + case CORE_LYNX: return lynxMenu(); +#endif #ifdef ENABLE_VECTREX case CORE_VECTREX: return vectrexMenu(); #endif diff --git a/Cart_Reader/Config.h b/Cart_Reader/Config.h index 474c10b..2ad5b27 100644 --- a/Cart_Reader/Config.h +++ b/Cart_Reader/Config.h @@ -89,6 +89,13 @@ /****/ +/* [ Atari LYNX --------------------------------------------------- ] +*/ + +//#define ENABLE_LYNX + +/****/ + /* [ Benesse Pocket Challenge W ----------------------------------- ] */ diff --git a/Cart_Reader/LYNX.ino b/Cart_Reader/LYNX.ino new file mode 100644 index 0000000..71d9991 --- /dev/null +++ b/Cart_Reader/LYNX.ino @@ -0,0 +1,263 @@ +//****************************************** +// ATARI LYNX MODULE +//****************************************** +// +// For use with SNES-Lynx adapter +// +----+ +// | 1 |- GND +// | 2 |- D3 +// | 3 |- D2 +// | 4 |- D4 +// | 5 |- D1 +// | 6 |- D5 +// | 7 |- D0 +// | 8 |- D6 +// | 9 |- D7 +// | 10 |- /OE +// | 11 |- A1 +// | 12 |- A2 +// | 13 |- A3 +// | 14 |- A6 +// | 15 |- A4 +// | 16 |- A5 +// | 17 |- A0 +// | 18 |- A7 +// | 19 |- A16 +// | 20 |- A17 +// | 21 |- A18 +// | 22 |- A19 +// | 23 |- A15 +// | 24 |- A14 +// | 25 |- A13 +// | 26 |- A12 +// | 27 |- /WE +// | 28 |- A8 +// | 29 |- A9 +// | 30 |- A10 +// | 31 |- VCC +// | 32 |- AUDIN +// | 33 |- VCC +// | 34 |- SWVCC +// +----+ +// +// By @partlyhuman +// This implementation would not be possible without the invaluable +// documentation on +// https://atarilynxvault.com/ +// by Igor (@theatarigamer) of K-Retro Gaming / Atari Lynx Vault +// and the reference implementation of the Lynx Cart Programmer Pi-Hat +// https://bitbucket.org/atarilynx/lynx/src/master/ +// by Karri Kaksonen (whitelynx.fi) and Igor as well as countless contributions +// by the Atari Lynx community +// +// Version 1.0 +// Future enhancements +// 1. EEPROM read/write +// 2. Homebrew flash cart programming +// +#ifdef ENABLE_LYNX + +#pragma region DEFS + +#define LYNX_HEADER_SIZE 64 +#define LYNX_WE 8 +#define LYNX_OE 9 +#define LYNX_AUDIN 46 +#define LYNX_BLOCKADDR 2048UL +#define LYNX_BLOCKCOUNT 256UL +// Includes \0 +static const char LYNX[5] = "LYNX"; + +// Cart information +static bool lynxUseAudin; +static uint16_t lynxBlockSize; + +#pragma region LOWLEVEL + +void setup_LYNX() { + setVoltage(VOLTS_SET_5V); + + // Address pins output + // A0-7, A8-A16 (A11 doesn't exist) + DDRF = 0xff; + DDRK = 0xff; + DDRL = 0xff; + + // Data pins input + DDRC = 0x00; + + // Control pins output + // CE is tied low, not accessible + pinMode(LYNX_WE, OUTPUT); + pinMode(LYNX_OE, OUTPUT); + pinMode(LYNX_AUDIN, OUTPUT); + digitalWrite(LYNX_WE, HIGH); + digitalWrite(LYNX_OE, HIGH); + digitalWrite(LYNX_AUDIN, HIGH); + + strcpy(romName, LYNX); + mode = CORE_LYNX; +} + +static void dataDir_LYNX(byte direction) { + DDRC = (direction == OUTPUT) ? 0xff : 0x00; +} + +static uint8_t readByte_LYNX(uint32_t addr, uint8_t audin = 0) { + digitalWrite(LYNX_OE, HIGH); + PORTF = addr & 0xff; + PORTK = (addr >> 8) & 0xff; + PORTL = ((addr >> 16) & 0b111) | (audin << 3); + digitalWrite(LYNX_OE, LOW); + delayMicroseconds(20); + uint8_t data = PINC; + digitalWrite(LYNX_OE, HIGH); + return data; +} + +#pragma region HIGHLEVEL + +static bool detectBlockSize_LYNX() { + lynxUseAudin = false; + lynxBlockSize = 0; + + int i; + uint8_t block[LYNX_BLOCKADDR]; + for (i = 0; i < LYNX_BLOCKADDR; i++) { + block[i] = readByte_LYNX(i, 0); + } + + for (i = 0; i < LYNX_BLOCKADDR; i++) { + // If any differences are detected when AUDIN=1, + // AUDIN is used to bankswitch + // meaning we also use the maximum block size + // (1024kb cart / 256 blocks = 4kb block bank switched between two + // lower/upper 2kb blocks) + if (block[i] != readByte_LYNX(i, 1)) { + lynxUseAudin = true; + lynxBlockSize = 2048; + return true; + } + } + + // Use the already-dumped 2KB to detect mirroring in a small sample + // Valid cart sizes of 128kb, 256kb, 512kb / 256 blocks + // = block sizes of 512b, 1024b, 2048b + const size_t DETECT_BYTES = 128; + for (i = 0; i < DETECT_BYTES; i++) { + if (block[i] != block[i + 256]) { + lynxBlockSize = max(lynxBlockSize, 512); + } + if (block[i] != block[i + 512]) { + lynxBlockSize = max(lynxBlockSize, 1024); + } + if (block[i] != block[i + 1024]) { + lynxBlockSize = max(lynxBlockSize, 2048); + } + } + + return (lynxBlockSize > 0); +} + +static bool detectCart_LYNX() { + // Could omit logging to save a few bytes + display_Clear(); + println_Msg(F("Identifying...")); + if (!detectBlockSize_LYNX()) { + print_STR(error_STR, false); + display_Update(); + wait(); + resetArduino(); + } + print_Msg(F("AUDIN=")); + print_Msg(lynxUseAudin); + print_Msg(F(" BLOCK=")); + println_Msg(lynxBlockSize); + display_Update(); +} + +static void writeHeader_LYNX() { + char header[LYNX_HEADER_SIZE] = {}; + // Magic number + strcpy(header, LYNX); + // Cart name (dummy) + strcpy(header + 10, LYNX); + // Manufacturer (dummy) + strcpy(header + 42, LYNX); + // Version + header[8] = 1; + // Bank 0 page size + header[4] = lynxBlockSize & 0xff; + // Bank 1 page size + header[5] = (lynxBlockSize >> 8) & 0xff; + // AUDIN used + header[59] = lynxUseAudin; + // TODO detect EEPROM? + // header[60] = lynxUseEeprom; + myFile.write(header, LYNX_HEADER_SIZE); +} + +static void readROM_LYNX() { + uint8_t block[lynxBlockSize]; + uint32_t i; + + dataDir_LYNX(INPUT); + + // The upper part of the address is used as a block address + // There are always 256 blocks, but the size of the block can vary + // So outer loop always steps through block addresses + + const uint32_t upto = LYNX_BLOCKCOUNT * LYNX_BLOCKADDR; + for (uint32_t blockAddr = 0; blockAddr < upto; blockAddr += LYNX_BLOCKADDR) { + draw_progressbar(blockAddr, upto); + blinkLED(); + + if (lynxUseAudin) { + // AUDIN bank switching uses a 4kb block split to 2 banks + for (i = 0; i < lynxBlockSize / 2; i++) { + block[i] = readByte_LYNX(blockAddr + i, 0); + } + for (; i < lynxBlockSize; i++) { + block[i] = readByte_LYNX(blockAddr + i - (lynxBlockSize / 2), 1); + } + } else { + for (i = 0; i < lynxBlockSize; i++) { + block[i] = readByte_LYNX(i + blockAddr); + } + } + + myFile.write(block, lynxBlockSize); + } + draw_progressbar(upto, upto); +} + +#pragma region MENU + +static const char* const menuOptionsLYNX[] PROGMEM = {FSTRING_READ_ROM, + FSTRING_RESET}; + +void lynxMenu() { + size_t menuCount = sizeof(menuOptionsLYNX) / sizeof(menuOptionsLYNX[0]); + convertPgm(menuOptionsLYNX, menuCount); + uint8_t mainMenu = question_box(F("LYNX MENU"), menuOptions, menuCount, 0); + display_Clear(); + display_Update(); + + switch (mainMenu) { + case 0: + sd.chdir("/"); + createFolderAndOpenFile(LYNX, "ROM", romName, "lnx"); + detectCart_LYNX(); + writeHeader_LYNX(); + readROM_LYNX(); + myFile.close(); + sd.chdir("/"); + compareCRC("lynx.txt", 0, true, LYNX_HEADER_SIZE); + print_STR(done_STR, true); + display_Update(); + wait(); + break; + } +} + +#endif \ No newline at end of file diff --git a/Cart_Reader/OSCR.h b/Cart_Reader/OSCR.h index 6381d86..3cdeee1 100644 --- a/Cart_Reader/OSCR.h +++ b/Cart_Reader/OSCR.h @@ -214,6 +214,9 @@ enum CORES: uint8_t { # ifdef ENABLE_7800 CORE_7800, # endif +# ifdef ENABLE_LYNX + CORE_LYNX, +# endif # ifdef ENABLE_VECTREX CORE_VECTREX, # endif @@ -302,6 +305,9 @@ enum SYSTEM_MENU: uint8_t { # if defined(ENABLE_7800) SYSTEM_MENU_7800, # endif +# if defined(ENABLE_LYNX) + SYSTEM_MENU_LYNX, +# endif # if defined(ENABLE_VECTREX) SYSTEM_MENU_VECTREX, # endif diff --git a/sd/lynx.txt b/sd/lynx.txt new file mode 100644 index 0000000..662124b --- /dev/null +++ b/sd/lynx.txt @@ -0,0 +1,1140 @@ +[BIOS] Atari Lynx (World).lnx +0D973C9D + +2048 (World) (Aftermarket) (Unl).lnx +A73938F5 + +4Ttude (World) (v1.2) (Aftermarket) (Unl).lnx +3941A51D + +4Ttude (World) (v1.1) (Aftermarket) (Unl).lnx +11F62511 + +4Ttude (World) (v1.0) (Atari Lynx 30th Birthday) (Aftermarket) (Unl).lnx +04444023 + +8bit-Dungeon (World) (Demo) (Aftermarket) (Unl).lnx +4130BB85 + +8bit-Slicks (World) (Demo) (Aftermarket) (Unl).lnx +7C3B36B6 + +A.P.B. (USA, Europe).lnx +F6FB48FB + +Alien (World) (v1.06) (Aftermarket) (Unl).lnx +A7B2C685 + +Alien (World) (v1.04) (Aftermarket) (Unl).lnx +447A8386 + +Alien (World) (v1.03) (Aftermarket) (Unl).lnx +4F46E18B + +Alien (World) (v1.02) (Aftermarket) (Unl).lnx +0F5C46C3 + +Alien vs Predator (USA) (Proto) (1993-12-17).lnx +540E9BB7 + +Allein (World) (De) (Aftermarket) (Unl).lnx +D5376D02 + +Always Winter, Never Christmas (World) (Aftermarket) (Unl).lnx +D4ECEF80 + +Angry Motes (World) (Aftermarket) (Unl).lnx +D6220C93 + +Anti A-Bomb Aircraft Artillery (World) (v1.0) (Aftermarket) (Unl).lnx +6FFABBFB + +Anti A-Bomb Aircraft Artillery (World) (v1.1) (Aftermarket) (Unl).lnx +004EA85A + +Assembloids (World) (v1.00) (Digital) (Atari Lynx 30th Birthday) (Aftermarket) (Unl).lnx +2A545F5A + +Assembloids (World) (v1.04) (Digital) (Aftermarket) (Unl).lnx +9762FA55 + +Asteroids Chasers (World) (2020-08-16) (Proto) (Aftermarket) (Unl).lnx +EF51810F + +Asteroids Chasers (World) (2020-08-14) (Proto) (Aftermarket) (Unl).lnx +5D3CD51D + +Atari Lynx Pong (World) (Demo) (Aftermarket) (Unl).lnx +17BD3083 + +Atari Tabletops Collection (World) (v1.1) (Aftermarket) (Unl).lnx +530B0553 + +Atari Tabletops Collection (World) (v1.0) (Aftermarket) (Unl).lnx +E1BB8C92 + +Atomix (World) (Proto) (Aftermarket) (Unl).lnx +A4570EF5 + +Awesome Golf (USA, Europe).lnx +0483CD2A + +Banana Ghost (World) (v0.2) (Proto) (Aftermarket) (Unl).lnx +CD8D53F1 + +Banana Ghost (World) (v0.1) (Proto) (Aftermarket) (Unl).lnx +70C345F8 + +Bars (World) (Proto) (Aftermarket) (Unl).lnx +87AC2E76 + +Baseball Heroes (USA, Europe).lnx +3943C116 + +Basketbrawl (USA, Europe).lnx +4161BB4A + +Bastion (World) (Demo) (Aftermarket) (Unl).lnx +023451A9 + +Bathman (World) (Aftermarket) (Unl).lnx +2B842BFF + +Batman Returns (USA, Europe).lnx +277F82C2 + +Battle Wheels (USA, Europe).lnx +779FAECE + +Battlespace (World) (Demo) (Aftermarket) (Unl).lnx +9CF351E3 + +Battlezone 2000 (USA, Europe).lnx +30FEE726 + +Befok (World) (Proto) (Aftermarket) (Unl).lnx +AFE60481 + +Bezerkoids (World) (Proto 3) (Aftermarket) (Unl).lnx +728EEB4E + +Bezerkoids (World) (Proto 2) (Aftermarket) (Unl).lnx +E2E11B2D + +Bezerkoids (World) (Proto 1) (Aftermarket) (Unl).lnx +6BDF6D54 + +Bill & Ted's Excellent Adventure (USA, Europe).lnx +143A313E + +Biniax (World) (v1.0) (Aftermarket) (Unl).lnx +FDCA4017 + +Biniax (World) (v0.1) (Beta) (Aftermarket) (Unl).lnx +613658AB + +Biniax (World) (v0.2) (Beta) (Aftermarket) (Unl).lnx +D65DDBC3 + +Biniax (World) (v0.3) (Beta) (Aftermarket) (Unl).lnx +5A3F90A0 + +Bitchy (World) (Digital) (Aftermarket) (Unl).lnx +21968CB7 + +Black Pit (World) (Proto) (Aftermarket) (Unl).lnx +029DC551 + +Block Out (USA, Europe).lnx +3CD75DF3 + +Blue Lightning (World) (Demo).lnx +BFE36525 + +Blue Lightning (World).lnx +DAF587B1 + +Bobo (World) (Proto) (Aftermarket) (Unl).lnx +73D93B13 + +Booster (World) (v0.3) (Demo) (Aftermarket) (Unl).lnx +260729F1 + +Booster - Xmas Edition (World) (Demo) (Aftermarket) (Unl).lnx +56A1FD2A + +Bowling Area (World) (Proto) (Aftermarket) (Unl).lnx +CEDFAFEF + +Bowling Beast (World) (Demo) (Aftermarket) (Unl).lnx +C1DF36BA + +Bubble Trouble (USA, Europe).lnx +333DAECE + +Bug Hunt (World) (Proto) (Aftermarket) (Unl).lnx +5216089A + +Bug's Lynx, A (World) (2020-04-03) (Demo) (Aftermarket) (Unl).lnx +393E6366 + +Bug's Lynx, A (World) (2018-11-02) (Demo) (eJagFest 2018) (Aftermarket) (Unl).lnx +2D39E747 + +Bug's Lynx, A (World) (2009-04-20) (Demo) (AC2k9) (Aftermarket) (Unl).lnx +9C6944FC + +Bug's Trip Redux, A (World) (Demo) (Aftermarket) (Unl).lnx +36D352BE + +C-Gull (World) (Aftermarket) (Unl).lnx +6A0C7417 + +Cabal (USA) (Demo).lnx +F8158766 + +California Games (World).lnx +A08F0B59 + +Captain Harlynx (World) (Demo) (Aftermarket) (Unl).lnx +E7F77035 + +Cardtest (World) (Beta) (Aftermarket) (Unl).lnx +FEEE8121 + +Castle of Khon-gis, The (World) (Demo) (Aftermarket) (Unl).lnx +7AB12A28 + +Catkanoid (World) (Aftermarket) (Unl).lnx +8327AC2F + +Centipede (USA) (Proto).lnx +97501709 + +Chase (World) (Aftermarket) (Unl).lnx +19F69733 + +Chasm Warden (World) (Proto) (Aftermarket) (Unl).lnx +B665AE38 + +Checkered Flag (USA, Europe).lnx +19C5A7A5 + +Chip's Challenge (World).lnx +6A5F53ED + +Choplifter (World) (Demo) (Aftermarket) (Unl).lnx +BCC7A948 + +Choplifter X (World) (Aftermarket) (Unl).lnx +B0DFD9D7 + +Circuit Dude (World) (Proto) (Aftermarket) (Unl).lnx +E205D7CA + +Clicks! (World) (Aftermarket) (Unl).lnx +7BDADD44 + +Columns (World) (Proto) (Aftermarket) (Unl).lnx +324DF7C6 + +Conquest of Zow (World) (Demo) (Unl).lnx +B02821FA + +Conquistador (World) (v1.1) (Aftermarket) (Unl).lnx +CACC4C9B + +Conquistador (World) (v1.0) (Aftermarket) (Unl).lnx +B96A8E67 + +Crate Challenge (World) (v1.1) (Aftermarket) (Unl).lnx +B0399E76 + +Critter Championship (World) (Demo) (Aftermarket) (Unl).lnx +E6411C5B + +Cross Bomber (World) (Aftermarket) (Unl).lnx +DFED165D + +Cross Chase (World) (v1.2) (Aftermarket) (Unl).lnx +6CC58515 + +Cross Chase (World) (v1.1) (Aftermarket) (Unl).lnx +7201F4BA + +Cross Chase (World) (v1.0) (Aftermarket) (Unl).lnx +9658B6ED + +Cross Horde (World) (Aftermarket) (Unl).lnx +140C018F + +Cross Shoot (World) (Aftermarket) (Unl).lnx +0025F4B5 + +Cross Snake (World) (Aftermarket) (Unl).lnx +5487A1FE + +Crystal Mines II (USA, Europe).lnx +AEC474C8 + +Crystal Mines II - Buried Treasure (World) (Aftermarket) (Unl).lnx +122EC861 + +CSS - Traffic Regulation (World) (Demo) (Aftermarket) (Unl).lnx +66178262 + +Cyberpunk Experiment 2037 (World) (2020-06-01) (Proto) (Aftermarket) (Unl).lnx +69E35D01 + +Cyberpunk Experiment 2037 (World) (2020-04-21) (Proto) (Aftermarket) (Unl).lnx +5440E425 + +Cyberpunk Experiment 2037 (World) (2020-04-14) (Proto) (Aftermarket) (Unl).lnx +8D320CBB + +Cyberpunk Experiment 2037 (World) (2020-04-13) (Proto) (Aftermarket) (Unl).lnx +DC547198 + +Daemon's Gate (USA) (Proto 1).lnx +99729395 + +Daemon's Gate (USA) (Proto 2).lnx +EF529ED4 + +Dance, Bro! (World) (Aftermarket) (Unl).lnx +76195D5B + +Desert Strike - Return to the Gulf (USA, Europe).lnx +B9AC1FE5 + +Devil's Show, The (World) (Demo) (Aftermarket) (Unl).lnx +900ABC85 + +Dice - Board Game (World) (Demo) (Aftermarket) (Unl).lnx +B7BCAE2E + +Dinolympics (USA, Europe).lnx +50386CFA + +Dirty Larry - Renegade Cop (USA, Europe).lnx +D565FBB7 + +Doom (World) (Demo) (Aftermarket) (Unl).lnx +55312DB7 + +Double Dragon (USA, Europe).lnx +FBFC0F05 + +Dracula - The Undead (USA, Europe).lnx +33BB74C7 + +DrunkWitch (World) (Aftermarket) (Unl).lnx +061D9270 + +Dungeddon (World) (v1.1) (Aftermarket) (Unl).lnx +6A9BD2A8 + +Dungeddon (World) (Beta) (Aftermarket) (Unl).lnx +4C62B125 + +Dynalynx (World) (v0.4) (Demo) (Aftermarket) (Unl).lnx +E5D57A21 + +Eggsavier's Cackleberry Rescue (World) (v1.1) (Aftermarket) (Unl).lnx +3240E8C4 + +Eggsavier's Cackleberry Rescue (World) (v1.0) (Aftermarket) (Unl).lnx +67FBF545 + +Electrocop (World).lnx +BD97116B + +Escape from Dungeon of Deja Vu (World) (Proto) (Aftermarket) (Unl).lnx +C294B0E8 + +European Soccer Challenge (USA, Europe).lnx +F83397F9 + +Eye of the Beholder (USA) (Proto 1).lnx +6BCEAA9C + +Eye of the Beholder (World) (Unl).lnx +F1B307CB + +Eye of the Beholder (USA) (Proto 2) (Aftermarket) (Unl).lnx +AE8C70F0 + +Fat Bobby (USA, Europe).lnx +9034EE27 + +Fidelity Ultimate Chess Challenge, The (USA, Europe).lnx +7E4B5945 + +Final Melee (World) (Aftermarket) (Unl).lnx +E5E9C13E + +Find a Way to My Heart (World) (Aftermarket) (Unl).lnx +F047AA45 + +Find a Way to My Heart (World) (Atari Lynx 30th Birthday) (Aftermarket) (Unl).lnx +9B688FE5 + +Fishing for Atari (World) (Aftermarket) (Unl).lnx +6F0DD4D6 + +Fission (World) (v0.6) (Proto) (Aftermarket) (Unl).lnx +12E434F4 + +Fortress Fighter (World) (Proto) (Aftermarket) (Unl).lnx +51049932 + +Friendly (World) (Demo) (Unl).lnx +68B0A428 + +Full Court Press (USA) (Proto).lnx +FF9E0126 + +Game of Life (World) (Demo) (Aftermarket) (Unl).lnx +0C356595 + +Garci (World) (Proto) (Aftermarket) (Unl).lnx +170DCF2D + +Gates of Zendocon (World).lnx +494CC568 + +Gauntlet - The Third Encounter (World).lnx +7F0EC7AD + +Gauntlet - The Third Encounter (World) (Beta) (1990-06-04).lnx +DCD723E3 + +Geoduel (USA) (Demo).lnx +BD4BF2B0 + +Gift Catcher (World) (Aftermarket) (Unl).lnx +8C6A089A + +Glob Shoot (World) (v0.62) (Proto) (Aftermarket) (Unl).lnx +D818CA70 + +Gnop! (World) (v0.6) (Proto) (Aftermarket) (Unl).lnx +1001374B + +Gnop! (World) (v0.4) (Proto) (Aftermarket) (Unl).lnx +D41A9D04 + +Gomoku (World) (Aftermarket) (Unl).lnx +A19DB957 + +Gordo 106 (USA, Europe).lnx +D20A85FC + +Grail of the Lava Kingdom, The (World) (Proto) (Aftermarket) (Unl).lnx +B639A6A6 + +Griel's Quest for the Sangraal (World) (v0.4) (Proto) (Aftermarket) (Unl).lnx +8A2ADADD + +Griel's Quest for the Sangraal (World) (v0.3) (Proto) (Aftermarket) (Unl).lnx +731BB543 + +Griel's Quest for the Sangraal (World) (v0.2) (Proto) (Aftermarket) (Unl).lnx +6DDB5C69 + +Griel's Quest for the Sangraal (World) (v0.1) (Proto) (Aftermarket) (Unl).lnx +79B96056 + +Grime 6502 (World) (Demo) (Aftermarket) (Unl).lnx +E5435DB5 + +Grogger (World) (Aftermarket) (Unl).lnx +79AEC28E + +Growing Ties (World) (Aftermarket) (Unl).lnx +2CA3C6DB + +Guardians, The - Storm Over Doria (USA) (Proto 2).lnx +8F0E72C6 + +Guardians, The - Storm Over Doria (USA) (Proto 1).lnx +0E335AA8 + +Hard Drivin' (USA, Europe).lnx +6DF63834 + +Hero Dust (World) (Proto) (Aftermarket) (Unl).lnx +2CAFA643 + +Hockey (USA, Europe).lnx +E8B45707 + +Hotdog (World) (Demo) (Aftermarket) (Unl).lnx +CB4CD341 + +Hungry Monsters (World) (v1.1) (Aftermarket) (Unl).lnx +501935C4 + +Hungry Monsters (World) (v1.0) (Retro Platform Jam #4) (Aftermarket) (Unl).lnx +8B30B6EB + +Hydra (USA, Europe).lnx +E3041C6C + +Inside World, The (World) (Demo) (Aftermarket) (Unl).lnx +8174AFE6 + +Invaders (World) (Demo) (Aftermarket) (Unl).lnx +6445224A + +Ishido - The Way of Stones (USA, Europe).lnx +5CF8BBF0 + +Jelly Beans (World) (Demo) (Aftermarket) (Unl).lnx +E798DEBC + +JetPack Hero (World) (Proto) (Aftermarket) (Unl).lnx +1FE4D98C + +Jimmy Connors' Tennis (USA, Europe).lnx +2455B6CF + +Joust (USA, Europe).lnx +5DBA792A + +Jungle Jack (World) (Aftermarket) (Unl).lnx +6C7E8360 + +Jungle Jack (World) (Beta) (Aftermarket) (Unl).lnx +9C4C4B47 + +Karateboy (World) (v1.1) (Demo) (Aftermarket) (Unl).lnx +DFDF6B07 + +Karateboy (World) (v1.0) (Demo) (Aftermarket) (Unl).lnx +73F4CDFB + +Kistenschieben (World) (v0.2) (Demo) (Aftermarket) (Unl).lnx +6A56A0B2 + +Klax (World).lnx +A53649F1 + +Klax (World) (Beta).lnx +4D5D94F4 + +Klondike (World) (Beta 2) (Aftermarket) (Unl).lnx +45822E44 + +Klondike (World) (Beta 1) (Aftermarket) (Unl).lnx +0CB83FEC + +Knight Moves (World) (Aftermarket) (Unl).lnx +4CA0654D + +Knightmare (World) (v1.1) (Aftermarket) (Unl).lnx +C8610FCB + +Knightmare (World) (v1.0) (Aftermarket) (Unl).lnx +C40D52AB + +Krazy Ace - Miniature Golf (USA, Europe).lnx +BED5BA2B + +Krow (World) (v1.01) (Demo) (Aftermarket) (Unl).lnx +03FC9FFD + +Kung Food (USA, Europe).lnx +CD1BD405 + +Lawnmower (World) (v1.1) (Aftermarket) (Unl).lnx +3E472554 + +Lemmings (USA, Europe).lnx +39B9B8CC + +Lexis (World) (Aftermarket) (Unl).lnx +0271B6E9 + +Limny (World) (Demo) (Aftermarket) (Unl).lnx +E419F250 + +Lode Runner (World) (Proto 1) (Aftermarket) (Unl).lnx +0E702444 + +Lode Runner (World) (Proto 2) (Aftermarket) (Unl).lnx +AE5AC8A9 + +Loopz (USA) (v0.06) (Proto) (1992-11-05).lnx +B1C25EF1 + +Loopz (USA) (v0.04) (Proto) (1992-09-16).lnx +D2B07D4D + +Luchsenstein 3D (World) (Demo) (Aftermarket) (Unl).lnx +3D3B2824 + +Lynx Blast (World) (Demo) (Aftermarket) (Unl).lnx +BB06A203 + +Lynx Casino (USA, Europe).lnx +1091A268 + +Lynx II Production Test Program (USA).lnx +28ADA019 + +Lynx Ops (World) (v0.2) (Proto) (Aftermarket) (Unl).lnx +A9F5EE4D + +Lynx Ops (World) (v0.1) (Proto) (Aftermarket) (Unl).lnx +4764673C + +Lynx Othello (World) (Aftermarket) (Unl).lnx +D659ADF8 + +Lynx Quest (World) (v1.04) (Aftermarket) (Unl).lnx +EE19D1AE + +Lynx Quest (World) (v1.00) (Atari Lynx 30th Birthday) (Aftermarket) (Unl).lnx +83080F5E + +Lynx Sketch (World) (Demo) (Aftermarket) (Unl).lnx +85DDFC28 + +Lynx Tris (World) (Aftermarket) (Unl).lnx +09A5D4E2 + +Lynx Tris (World) (Beta) (Aftermarket) (Unl).lnx +D8128C49 + +Lynxopoly (World) (Aftermarket) (Unl).lnx +CA075B54 + +Maldetetemagic Vol.1 (World) (Aftermarket) (Unl).lnx +04697ABA + +Maldetetemagic Vol.2 (World) (Aftermarket) (Unl).lnx +149416AB + +Malibu Bikini Volleyball (USA, Europe).lnx +ABA6DA3D + +Malibu Bikini Volleyball (USA, Europe) (Beta) (1993-05-11).lnx +4329084A + +Marble Madness (World) (Aftermarket) (Unl).lnx +323AF126 + +Marble Madness (World) (Beta) (Aftermarket) (Unl).lnx +C7019B83 + +Marlboro Go! (Europe) (Proto 2).lnx +C3FA0D4D + +Marlboro Go! (Europe) (Proto 1).lnx +B17A36AD + +MegaPak Vol.1 (World) (Aftermarket) (Unl).lnx +CA7CF30B + +Microvaders (World) (Proto) (Aftermarket) (Unl).lnx +EF918269 + +Mines 2 (World) (Demo) (Unl).lnx +90C28F57 + +Minimal (World) (Proto) (Aftermarket) (Unl).lnx +E6FC3FCD + +Moon Runner (World) (Proto) (Aftermarket) (Unl).lnx +4D2D4200 + +Mortal Kombat (World) (Aftermarket) (Unl).lnx +96729B18 + +Ms. Pac-Man (World).lnx +7DE3783A + +MultiPong 1k (World) (Aftermarket) (Unl).lnx +EB8FF024 + +Naughty (World) (Demo) (Aftermarket) (Unl).lnx +F40F460A + +Nezumi-kun to Poker Shiyouyo (World) (Ja) (v1.2) (Aftermarket) (Unl).lnx +B5022B5D + +Nezumi-kun to Poker Shiyouyo (World) (Ja) (v1.0) (Aftermarket) (Unl).lnx +091F2141 + +Nezumi-kun to Poker Shiyouyo (World) (Ja) (v1.1) (Aftermarket) (Unl).lnx +C8B6B95C + +NFL Football (USA, Europe).lnx +006FD398 + +Ninja Gaiden (USA, Europe).lnx +22D47D51 + +Ninja Gaiden III - The Ancient Ship of Doom (USA, Europe).lnx +F3E3F811 + +Ninja Nerd (USA) (Demo).lnx +F428008D + +No Time (World) (Demo) (Aftermarket) (Unl).lnx +33529D7C + +Nomad Rally 2018 (World) (Demo) (Aftermarket) (Unl).lnx +84BBA7E2 + +Nutmeg (World) (Aftermarket) (Unl).lnx +206B2E55 + +Nyan Cat (World) (Demo) (Aftermarket) (Unl).lnx +B99C78D2 + +Odynexus - Journey to Ithaca (World) (v0.98) (Demo) (Aftermarket) (Unl).lnx +24872C11 + +Odynexus - Journey to Ithaca (World) (Demo) (Silly Venture 2019) (Aftermarket) (Unl).lnx +AAD8A554 + +On Duty (World) (Aftermarket) (Unl).lnx +D51B08DA + +On Duty (World) (Beta) (Aftermarket) (Unl).lnx +351A8834 + +Othello (World) (Aftermarket) (Unl).lnx +294065DF + +Ouragan (World) (Demo 2) (Aftermarket) (Unl).lnx +B6B97F31 + +Ouragan (World) (Demo 1) (Aftermarket) (Unl).lnx +81C9B099 + +Oxymore (World) (Proto) (Aftermarket) (Unl).lnx +C01DCAFE + +Pac-Land (USA, Europe).lnx +AA50DD22 + +Paperboy (World).lnx +4CDFBD57 + +Paraply (World) (Demo) (Aftermarket) (Unl).lnx +24538B39 + +Peg Solitaire (World) (v1.3) (Aftermarket) (Unl).lnx +2E71412A + +Peg Solitaire (World) (v1.2) (512k) (Aftermarket) (Unl).lnx +A19B9319 + +Peg Solitaire (World) (v1.2) (256k) (Aftermarket) (Unl).lnx +3126484C + +Peg Solitaire (World) (v1.1) (Aftermarket) (Unl).lnx +E6800182 + +Peg Solitaire (World) (v1.0) (Aftermarket) (Unl).lnx +75D7DA12 + +Pinball Jam (USA, Europe).lnx +14D38CA7 + +Pit-Fighter (USA, Europe).lnx +2393135F + +Pit-Fighter (USA, Europe) (Beta) (1992-10-13).lnx +E7A5A4D0 + +Poachin' Jeff (World) (Proto) (Aftermarket) (Unl).lnx +D711FF77 + +Poker (Europe) (Fr) (Proto).lnx +C45BCD62 + +Pong (World) (Demo 2) (Aftermarket) (Unl).lnx +8AE0062A + +Pong (World) (Demo 1) (Aftermarket) (Unl).lnx +9710A29F + +Pong-4-Fun (World) (Proto) (Aftermarket) (Unl).lnx +45EAC2BA + +Pounce! (USA) (Proto).lnx +5D1ABFE7 + +Power Factor (USA, Europe).lnx +99C42034 + +Push Around the World (World) (Demo) (Aftermarket) (Unl).lnx +569018D9 + +Puyo Puyo (World) (Demo 2) (Aftermarket) (Unl).lnx +F5FEE7F8 + +Puyo Puyo (World) (Demo 1) (Aftermarket) (Unl).lnx +82175988 + +Puzzler 2000 (World) (Proto) (Unl).lnx +F6F43C87 + +QIX (USA, Europe).lnx +B9881423 + +RabbiLynx EggCatcher (World) (Demo) (Aftermarket) (Unl).lnx +3021F7D9 + +Raid on TriCity (World) (Beta) (Aftermarket) (Unl).lnx +F68B82C5 + +Raid on TriCity - First Impact (World) (v1.01) (Aftermarket) (Unl).lnx +0FA40782 + +Raid on TriCity - Second Wave (World) (Digital) (Aftermarket) (Unl).lnx +0B737767 + +Raiden (USA) (Beta).lnx +BCD10C3A + +Raiden (World) (Aftermarket) (Unl).lnx +689F31A4 + +Rainbow (World) (Proto) (Aftermarket) (Unl).lnx +589B1DD1 + +RAM cart (USA) (Proto) (Program).lnx +74EC2B2B + +Rampage (World).lnx +B10B7C8E + +Rampart (USA, Europe).lnx +139F301D + +Rapid Racer (World) (Demo) (Aftermarket) (Unl).lnx +22AE9CE0 + +RED Against the Machines (World) (v1.11) (Demo) (Aftermarket) (Unl).lnx +8985A544 + +RED Against the Machines (World) (v1.01) (Demo) (LynXmas 2020) (Aftermarket) (Unl).lnx +EAB37590 + +Red Square (World) (v1.1) (Aftermarket) (Unl).lnx +7E56D55A + +Red Square (World) (v1.0) (Aftermarket) (Unl).lnx +A7BF34E9 + +Remnant - Planar Wars 3D (World) (2000-05-24) (Proto) (Aftermarket) (Unl).lnx +92C8F0FA + +Return of the Space Coyote (World) (Demo) (Aftermarket) (Unl).lnx +5223314C + +Reussite (World) (Demo) (Aftermarket) (Unl).lnx +5F1D1DB5 + +Road Riot 4WD (USA) (Proto 4).lnx +69959A3B + +Road Riot 4WD (USA) (Proto 3).lnx +82555EEB + +Road Riot 4WD (USA) (Proto 2).lnx +ECCEA0D5 + +Road Riot 4WD (USA) (Proto 1).lnx +BFECEFC3 + +RoadBlasters (World).lnx +6867E80C + +Robo-Squash (World).lnx +D1DFF2B2 + +Robotron 2084 (USA, Europe).lnx +7A6049B5 + +Rolling Thunder (USA) (Proto).lnx +467F8FD9 + +Running Knight (World) (Aftermarket) (Unl).lnx +86A4604B + +Rygar (World).lnx +67E5BDBA + +S.T.U.N. Runner (USA, Europe).lnx +8595C40B + +Santa Factory (World) (Aftermarket) (Unl).lnx +23A6C3FA + +Santa's Empty Sack (World) (Aftermarket) (Unl).lnx +6F412521 + +Saving Santa Tree (World) (Aftermarket) (Unl).lnx +E1620994 + +Scooternia (World) (Demo) (Aftermarket) (Unl).lnx +7606DCAC + +Scrapyard Dog (USA, Europe).lnx +BE166F3B + +Scroll of theTime Lords - Lovejoy Prologue (World) (Aftermarket) (Unl).lnx +3ABC850B + +Shadow of the Beast (USA, Europe).lnx +EB78BAA3 + +Shaken, Not Stirred (World) (v1.3) (Aftermarket) (Unl).lnx +28CAA192 + +Shaken, Not Stirred (World) (v1.2) (Aftermarket) (Unl).lnx +63A533EF + +Shaken, Not Stirred (World) (v1.1) (Aftermarket) (Unl).lnx +DA992C87 + +Shaken, Not Stirred (World) (v1.0) (Aftermarket) (Unl).lnx +F2F26C63 + +Shanghai (World).lnx +192BCD04 + +Shuriken (World) (Aftermarket) (Unl).lnx +0480CF82 + +Silas Adventure (World) (Aftermarket) (Unl).lnx +96861002 + +Silly Archery (World) (Aftermarket) (Unl).lnx +25ADD72D + +Silly Blaster (World) (Aftermarket) (Unl).lnx +5DD26C8D + +SillySis (World) (Aftermarket) (Unl).lnx +4AE7BA59 + +Sky Raider (World) (Demo) (Aftermarket) (Unl).lnx +D3DF927F + +Snake (World) (Proto) (Aftermarket) (Unl).lnx +1D695480 + +Snow Ball (World) (Aftermarket) (Unl).lnx +9E43E62B + +Solitaire (World) (2009-09-28) (karri) (Aftermarket) (Unl).lnx +31E6A372 + +Solitaire (World) (2009-08-31) (karri) (Aftermarket) (Unl).lnx +3C4CD1D2 + +Solitaire (World) (2009-08-28) (karri) (Aftermarket) (Unl).lnx +7FF033CE + +Solitaire (World) (2009-08-27) (karri) (Aftermarket) (Unl).lnx +B0605319 + +Solitaire (World) (Demo) (Inga) (Aftermarket) (Unl).lnx +0E500B32 + +Sorrow (World) (Proto) (Aftermarket) (Unl).lnx +8AA245D0 + +Space Domino (World) (Aftermarket) (Unl).lnx +1FA8F631 + +Space Lock & Space Shoot (World) (2005-05-16) (Proto) (Aftermarket) (Unl).lnx +DC62DF2D + +Space Lock & Space Shoot (World) (2005-04-25) (Proto) (Aftermarket) (Unl).lnx +A319FE86 + +Spacewar (USA) (Demo).lnx +59510CB3 + +Springtime Is Here (World) (Demo) (Aftermarket) (Unl).lnx +DEBF296D + +Star Blader (World) (Proto) (Aftermarket) (Unl).lnx +9E6B0448 + +Star Raiders (World) (Demo 2) (Aftermarket) (Unl).lnx +172A4801 + +Star Raiders (World) (Demo 1) (Aftermarket) (Unl).lnx +4B8DA90F + +Stardreamer (World) (Demo) (Aftermarket) (Unl).lnx +EB627DF8 + +Steel Talons (USA, Europe).lnx +5B2308ED + +Stinger (World) (Aftermarket) (Unl).lnx +AE4BE313 + +Sudoku (World) (Proto) (Aftermarket) (Unl).lnx +89DAA534 + +Super Asteroids, Missile Command (USA, Europe).lnx +2DA7E2A8 + +Super Off-Road (USA, Europe).lnx +690CAEB0 + +Super Skweek (USA, Europe).lnx +DFA61571 + +Swaping Tiles (World) (Proto) (Aftermarket) (Unl).lnx +B7A11711 + +Switchblade II (USA, Europe).lnx +13657705 + +Sybil's Nightmare Run (World) (Proto) (Aftermarket) (Unl).lnx +C81D2F44 + +Teen Dance (World) (Aftermarket) (Unl).lnx +C6D2879D + +Tetris Raiden (World) (Beta) (Aftermarket) (Unl).lnx +A17A420C + +Tetrisnoid (World) (Proto) (Aftermarket) (Unl).lnx +B563C066 + +Timeloop (World) (Aftermarket) (Unl).lnx +B8443CF3 + +Tiny Lynx Adventure (World) (Aftermarket) (Unl).lnx +A0A0FACB + +Titan (World) (Proto) (Aftermarket) (Unl).lnx +EB961C76 + +Todd's Adventures in Slime World (World).lnx +AE267E29 + +Toki (USA, Europe).lnx +156A4A4C + +Tournament Cyberball (USA, Europe).lnx +0590A9E3 + +Traffic Jam (World) (Demo) (Aftermarket) (Unl).lnx +A25FFA4A + +Tron-6-Fun (World) (Proto) (Aftermarket) (Unl).lnx +DE12024B + +Tropsy (World) (Proto) (Aftermarket) (Unl).lnx +39A94CF1 + +Turbo Sub (USA, Europe).lnx +A4B924D6 + +Turkey Puncher 3 - Hyper Punching (World) (Demo) (Aftermarket) (Unl).lnx +4A31546F + +Twitter Panic (World) (Proto) (Aftermarket) (Unl).lnx +98964EE8 + +Undergrounders (World) (Demo) (Aftermarket) (Unl).lnx +E16E1FBF + +Verbix (World) (Aftermarket) (Unl).lnx +4AA8CD45 + +Viking Child (USA, Europe).lnx +8D56828B + +Vindicators (USA) (Proto).lnx +6E084B3A + +Warbirds (USA, Europe).lnx +B946BA49 + +Weltenschlacht (World) (v0.41) (Demo) (Aftermarket) (Unl).lnx +A0C59271 + +Wizzy (World) (Proto) (Aftermarket) (Unl).lnx +74CEA6A3 + +Wolfenstein 3D (World) (Demo 2) (Aftermarket) (Unl).lnx +A1E7840F + +Wolfenstein 3D (World) (Demo 1) (Aftermarket) (Unl).lnx +101F072B + +Wolfman (USA) (Demo) (Kiosk).lnx +C5EBD679 + +World Class Soccer (USA, Europe).lnx +91233794 + +Xenophobe (World).lnx +9BED736D + +Xump - The Final Run (World) (v1.20) (Aftermarket) (Unl).lnx +577D7E7B + +Xump - The Final Run (World) (v1.00) (Aftermarket) (Unl).lnx +6A71421A + +Xump 2 - Back to Space (World) (Proto) (Aftermarket) (Unl).lnx +AED70CF1 + +Xybots (USA, Europe).lnx +89E2A595 + +Yastuna Volume 1 - The Alchemy of Cubes (World) (v1.01) (Digital) (Aftermarket) (Unl).lnx +6CF9CE19 + +Yastuna Volume 1 - The Alchemy of Cubes (World) (v1.00) (Digital) (Aftermarket) (Unl).lnx +A6FE24AF + +Yastuna Volume 2 - The Space Incident (World) (v1.01) (Digital) (Aftermarket) (Unl).lnx +21975E97 + +Yastuna Volume 2 - The Space Incident (World) (v1.00) (Digital) (Aftermarket) (Unl).lnx +6B09D810 + +YNXA (World) (Game Jam Edition) (Digital) (Aftermarket) (Unl).lnx +DC8713EE + +YNXA - Leaf and the Guardians of Time (World) (Proto 2) (LynxJam23) (Aftermarket) (Unl).lnx +D9259C78 + +YNXA - Leaf and the Guardians of Time (World) (Proto 1) (Aftermarket) (Unl).lnx +AFCC0558 + +Z.A.P. (World) (Aftermarket) (Unl).lnx +56E49935 + +Zaku (World) (Unl).lnx +4EA845DA + +Zaku (World) (Beta) (Unl).lnx +B3747C71 + +Zaku Puzzle (World) (Demo) (Aftermarket) (Unl).lnx +F0177330 + +Zap (World) (Demo) (Aftermarket) (Unl).lnx +7A3FFB22 + +Zarlor Mercenary (World).lnx +CB27199D + From 715e6427bbb75643ed2df3ba86357a7ec5104995 Mon Sep 17 00:00:00 2001 From: Roger Braunstein Date: Sun, 7 Jul 2024 14:24:34 -0700 Subject: [PATCH 2/5] Adds Atari Lynx to supported systems in readme --- README.md | 99 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 50 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index abb99e3..5f74261 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,63 @@ -![image](https://dl.dropboxusercontent.com/s/ioc5oewzcuvs8nz/logos.png?dl=1) +![image](https://dl.dropboxusercontent.com/s/ioc5oewzcuvs8nz/logos.png?dl=1) # Open Source Cartridge Reader -This project represents a community-driven effort to provide an easy to build and easy to modify cartridge dumper. -Its main purpose is to dump a video game's ROM and save file to an SD card without the need of a PC. +This project represents a community-driven effort to provide an easy to build and easy to modify cartridge dumper. +Its main purpose is to dump a video game's ROM and save file to an SD card without the need of a PC. -For any questions you can use the [Discussions](https://github.com/sanni/cartreader/discussions) and/or [Issues](https://github.com/sanni/cartreader/issues) section here on Github or visit the accompanying thread in the [Arduino Forum](http://forum.arduino.cc/index.php?topic=158974.9001), where you can reach most of the devs directly. +For any questions you can use the [Discussions](https://github.com/sanni/cartreader/discussions) and/or [Issues](https://github.com/sanni/cartreader/issues) section here on Github or visit the accompanying thread in the [Arduino Forum](http://forum.arduino.cc/index.php?topic=158974.9001), where you can reach most of the devs directly. -Also be sure to check the guides in the [Wiki](https://github.com/sanni/cartreader/wiki) too. +Also be sure to check the guides in the [Wiki](https://github.com/sanni/cartreader/wiki) too. -Happy making. 🔧🔨😊 +Happy making. 🔧🔨😊 -![image](https://dl.dropboxusercontent.com/s/3lrn7xh3f7h6jre/HW5_front.png?dl=1) +![image](https://dl.dropboxusercontent.com/s/3lrn7xh3f7h6jre/HW5_front.png?dl=1) -#### Features: -- Modular design -- Stand-alone operation -- Easy to modify open-source code -- Portable when used together with a power bank +#### Features: +- Modular design +- Stand-alone operation +- Easy to modify open-source code +- Portable when used together with a power bank -![image](https://dl.dropboxusercontent.com/s/w99hewh6ors3awb/HW5_side.png?dl=1) +![image](https://dl.dropboxusercontent.com/s/w99hewh6ors3awb/HW5_side.png?dl=1) -#### Supported Systems: -- NES/Famicom/Family Basic -- Super Nintendo/Super Famicom (including SF Memory, Satellaview, Sufami Turbo, and Game Processor RAM Cassettes) -- Nintendo 64 (including Controller Pak, Gameshark, and Xplorer 64) -- Game Boy Color (including GB Memory, Codebreaker, and Gameshark) -- Game Boy Advance -- Sega Mega Drive/Genesis -- Sega Master System +#### Supported Systems: +- NES/Famicom/Family Basic +- Super Nintendo/Super Famicom (including SF Memory, Satellaview, Sufami Turbo, and Game Processor RAM Cassettes) +- Nintendo 64 (including Controller Pak, Gameshark, and Xplorer 64) +- Game Boy Color (including GB Memory, Codebreaker, and Gameshark) +- Game Boy Advance +- Sega Mega Drive/Genesis +- Sega Master System -![image](https://dl.dropboxusercontent.com/s/oi7c2radgblylyz/HW5_slots.png?dl=1) +![image](https://dl.dropboxusercontent.com/s/oi7c2radgblylyz/HW5_slots.png?dl=1) -#### Supported with adapters: -- Virtual Boy -- Sega Game Gear -- Sega Mark III -- Sega SG-1000/SC-3000 -- Sega Cards -- PC Engine/TurboGrafx-16/SuperGrafx -- WonderSwan (Color) -- NeoGeo Pocket (Color) -- Intellivision -- ColecoVision -- Benesse Pocket Challenge W -- Benesse Pocket Challenge V2 -- Watara Supervision -- Atari 2600/5200/7800 -- Emerson Arcadia 2001 -- Fairchild Channel F -- Magnavox Odyssey 2/Philips Videopac+ -- Super A'Can -- MSX -- Pokémon Mini +#### Supported with adapters: +- Virtual Boy +- Sega Game Gear +- Sega Mark III +- Sega SG-1000/SC-3000 +- Sega Cards +- PC Engine/TurboGrafx-16/SuperGrafx +- WonderSwan (Color) +- NeoGeo Pocket (Color) +- Intellivision +- ColecoVision +- Benesse Pocket Challenge W +- Benesse Pocket Challenge V2 +- Watara Supervision +- Atari 2600/5200/7800 +- Atari Lynx +- Emerson Arcadia 2001 +- Fairchild Channel F +- Magnavox Odyssey 2/Philips Videopac+ +- Super A'Can +- MSX +- Pokémon Mini - Casio Loopy -- Commodore 64 -- Vectrex +- Commodore 64 +- Vectrex -#### Open Source Licenses: -- Software(everything in Cart_Reader folder) = GPL v3 -- Hardware(everything in hardware folder) = CC BY 4.0 -- Documentation(everything in the Wiki) = CC0 1.0 +#### Open Source Licenses: +- Software(everything in Cart_Reader folder) = GPL v3 +- Hardware(everything in hardware folder) = CC BY 4.0 +- Documentation(everything in the Wiki) = CC0 1.0 From a751f4f9a62f3f7f9daaf27a95f0882111922aff Mon Sep 17 00:00:00 2001 From: Roger Braunstein Date: Sun, 7 Jul 2024 15:37:32 -0700 Subject: [PATCH 3/5] Completely eliminates extra stack-allocated buffers --- Cart_Reader/LYNX.ino | 53 ++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/Cart_Reader/LYNX.ino b/Cart_Reader/LYNX.ino index 71d9991..4a519e3 100644 --- a/Cart_Reader/LYNX.ino +++ b/Cart_Reader/LYNX.ino @@ -121,19 +121,17 @@ static bool detectBlockSize_LYNX() { lynxUseAudin = false; lynxBlockSize = 0; - int i; - uint8_t block[LYNX_BLOCKADDR]; - for (i = 0; i < LYNX_BLOCKADDR; i++) { - block[i] = readByte_LYNX(i, 0); - } + // Somewhat arbitrary, however many bytes would be unlikely to be + // coincidentally mirrored + const size_t DETECT_BYTES = 128; - for (i = 0; i < LYNX_BLOCKADDR; i++) { + for (int i = 0; i < DETECT_BYTES; i++) { // If any differences are detected when AUDIN=1, // AUDIN is used to bankswitch // meaning we also use the maximum block size // (1024kb cart / 256 blocks = 4kb block bank switched between two // lower/upper 2kb blocks) - if (block[i] != readByte_LYNX(i, 1)) { + if (readByte_LYNX(i, 0) != readByte_LYNX(i, 1)) { lynxUseAudin = true; lynxBlockSize = 2048; return true; @@ -143,16 +141,14 @@ static bool detectBlockSize_LYNX() { // Use the already-dumped 2KB to detect mirroring in a small sample // Valid cart sizes of 128kb, 256kb, 512kb / 256 blocks // = block sizes of 512b, 1024b, 2048b - const size_t DETECT_BYTES = 128; - for (i = 0; i < DETECT_BYTES; i++) { - if (block[i] != block[i + 256]) { - lynxBlockSize = max(lynxBlockSize, 512); - } - if (block[i] != block[i + 512]) { - lynxBlockSize = max(lynxBlockSize, 1024); - } - if (block[i] != block[i + 1024]) { + for (int i = 0; i < DETECT_BYTES; i++) { + uint8_t b = readByte_LYNX(i); + if (b != readByte_LYNX(i + 1024)) { lynxBlockSize = max(lynxBlockSize, 2048); + } else if (b != readByte_LYNX(i + 512)) { + lynxBlockSize = max(lynxBlockSize, 1024); + } else if (b != readByte_LYNX(i + 256)) { + lynxBlockSize = max(lynxBlockSize, 512); } } @@ -197,16 +193,24 @@ static void writeHeader_LYNX() { myFile.write(header, LYNX_HEADER_SIZE); } -static void readROM_LYNX() { - uint8_t block[lynxBlockSize]; - uint32_t i; +// Saves memory by using existing sd buffer instead of a second block-sized buffer (which could be up to 2KB) +// Minimum block size is 512b, size of sdBuffer is 512b, all block sizes multiples of 512b, +// so we shouldn't need to check for leftovers... +static inline void ringBufferWrite_LYNX(uint32_t blocki, uint8_t byte) { + sdBuffer[blocki % 512] = byte; + if ((blocki + 1) % 512 == 0) { + myFile.write(sdBuffer, 512); + } +} +static void readROM_LYNX() { dataDir_LYNX(INPUT); // The upper part of the address is used as a block address // There are always 256 blocks, but the size of the block can vary // So outer loop always steps through block addresses + uint32_t i; const uint32_t upto = LYNX_BLOCKCOUNT * LYNX_BLOCKADDR; for (uint32_t blockAddr = 0; blockAddr < upto; blockAddr += LYNX_BLOCKADDR) { draw_progressbar(blockAddr, upto); @@ -215,26 +219,23 @@ static void readROM_LYNX() { if (lynxUseAudin) { // AUDIN bank switching uses a 4kb block split to 2 banks for (i = 0; i < lynxBlockSize / 2; i++) { - block[i] = readByte_LYNX(blockAddr + i, 0); + ringBufferWrite_LYNX(i, readByte_LYNX(blockAddr + i, 0)); } for (; i < lynxBlockSize; i++) { - block[i] = readByte_LYNX(blockAddr + i - (lynxBlockSize / 2), 1); + ringBufferWrite_LYNX(i, readByte_LYNX(blockAddr + i - (lynxBlockSize / 2), 1)); } } else { for (i = 0; i < lynxBlockSize; i++) { - block[i] = readByte_LYNX(i + blockAddr); + ringBufferWrite_LYNX(i, readByte_LYNX(i + blockAddr)); } } - - myFile.write(block, lynxBlockSize); } draw_progressbar(upto, upto); } #pragma region MENU -static const char* const menuOptionsLYNX[] PROGMEM = {FSTRING_READ_ROM, - FSTRING_RESET}; +static const char* const menuOptionsLYNX[] PROGMEM = {FSTRING_READ_ROM, FSTRING_RESET}; void lynxMenu() { size_t menuCount = sizeof(menuOptionsLYNX) / sizeof(menuOptionsLYNX[0]); From bfe13908327ec815a0aab328da4abb4acbb18b01 Mon Sep 17 00:00:00 2001 From: Roger Braunstein Date: Sun, 7 Jul 2024 16:12:02 -0700 Subject: [PATCH 4/5] Further optimize detection, combine loops --- Cart_Reader/LYNX.ino | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/Cart_Reader/LYNX.ino b/Cart_Reader/LYNX.ino index 4a519e3..3d67c3e 100644 --- a/Cart_Reader/LYNX.ino +++ b/Cart_Reader/LYNX.ino @@ -117,7 +117,13 @@ static uint8_t readByte_LYNX(uint32_t addr, uint8_t audin = 0) { #pragma region HIGHLEVEL -static bool detectBlockSize_LYNX() { +static bool detectBlockSize_LYNX() {} + +static bool detectCart_LYNX() { + // Could omit logging to save a few bytes + display_Clear(); + println_Msg(F("Identifying...")); + lynxUseAudin = false; lynxBlockSize = 0; @@ -126,23 +132,17 @@ static bool detectBlockSize_LYNX() { const size_t DETECT_BYTES = 128; for (int i = 0; i < DETECT_BYTES; i++) { - // If any differences are detected when AUDIN=1, - // AUDIN is used to bankswitch + uint8_t b = readByte_LYNX(i); + // If any differences are detected when AUDIN=1, AUDIN is used to bankswitch // meaning we also use the maximum block size - // (1024kb cart / 256 blocks = 4kb block bank switched between two - // lower/upper 2kb blocks) - if (readByte_LYNX(i, 0) != readByte_LYNX(i, 1)) { + // (1024kb cart / 256 blocks = 4kb block bank switched between lower/upper 2kb blocks) + if (b != readByte_LYNX(i, 1)) { lynxUseAudin = true; lynxBlockSize = 2048; - return true; + break; } - } - - // Use the already-dumped 2KB to detect mirroring in a small sample - // Valid cart sizes of 128kb, 256kb, 512kb / 256 blocks - // = block sizes of 512b, 1024b, 2048b - for (int i = 0; i < DETECT_BYTES; i++) { - uint8_t b = readByte_LYNX(i); + // Identify mirroring of largest stride + // Valid cart sizes of 128kb, 256kb, 512kb / 256 blocks = block sizes of 512b, 1024b, 2048b if (b != readByte_LYNX(i + 1024)) { lynxBlockSize = max(lynxBlockSize, 2048); } else if (b != readByte_LYNX(i + 512)) { @@ -152,14 +152,7 @@ static bool detectBlockSize_LYNX() { } } - return (lynxBlockSize > 0); -} - -static bool detectCart_LYNX() { - // Could omit logging to save a few bytes - display_Clear(); - println_Msg(F("Identifying...")); - if (!detectBlockSize_LYNX()) { + if (lynxBlockSize == 0) { print_STR(error_STR, false); display_Update(); wait(); From c7e365afbdbb8e08268a2700d9534c5a40bb88aa Mon Sep 17 00:00:00 2001 From: Roger Braunstein Date: Sun, 7 Jul 2024 16:13:13 -0700 Subject: [PATCH 5/5] Removes refactored out method --- Cart_Reader/LYNX.ino | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cart_Reader/LYNX.ino b/Cart_Reader/LYNX.ino index 3d67c3e..4338099 100644 --- a/Cart_Reader/LYNX.ino +++ b/Cart_Reader/LYNX.ino @@ -117,8 +117,6 @@ static uint8_t readByte_LYNX(uint32_t addr, uint8_t audin = 0) { #pragma region HIGHLEVEL -static bool detectBlockSize_LYNX() {} - static bool detectCart_LYNX() { // Could omit logging to save a few bytes display_Clear();