LX.NET Posted December 28, 2011 Share Posted December 28, 2011 As promised, here is my annotation of the Lynx boot rom. All comments to improve this are welcome. I noticed that the boot.src (source code to first decrypted frame) has some internal zp variable names. I will factor those in and improve the disassembly. That's for tomorrow, because it is getting too late now. Let me know what you guys think. ; Fill shift register for cartridge FE00 38 SEC FE01 80 0A BRA $FE0D FE03 90 04 BCC $FE09 FE05 8E 8B FD STX $FD8B ; Set cartridge output (bit to shift into register) to X FE08 18 CLC FE09 E8 INX ; FE0A 8E 87 FD STX $FD87 ; FE0D A2 02 LDX #$02 FE0F 8E 87 FD STX $FD87 ; SYSCTL1: Power on and strobe FE12 2A ROL A FE13 9C 8B FD STZ $FD8B ; Zero for bit to shift into register FE16 D0 EB BNE $FE03 FE18 60 RTS ; Clear all memory from $0003 to $FFFF to 0x00 FE19 64 01 STZ $01 FE1B A9 00 LDA #$00 FE1D 91 00 STA ($00),y FE1F C8 INY FE20 D0 FB BNE $FE1D FE22 E6 01 INC $01 FE24 D0 F7 BNE $FE1D ; Routine to initialize Mikey FE26 A2 0D LDX #$0D FE28 BD D8 FF LDA $FFD8,x FE2B BC CC FF LDY $FFCC,x FE2E 99 00 FD STA $FD00,y FE31 CA DEX FE32 D0 F4 BNE $FE28 ; Copy 256 bytes from $FEC1 to $5000 FE34 BD C1 FE LDA $FEC1,x FE37 9D 00 50 STA $5000,x FE3A E8 INX FE3B D0 F7 BNE $FE34 ; Start decryption process for first frame FE3D 64 05 STZ $05 ; Destination address of decrypted data (low) FE3F A9 02 LDA #$02 FE41 85 06 STA $06 ; Destination address of decrypted data (high) FE43 64 02 STZ $02 ; Initialize transition byte FE45 A9 00 LDA #$00 FE47 20 00 FE JSR #FE00 ; Call SetCartBlock routine ; Load RSA FE4A AD B2 FC LDA $FCB2 ; Read cart (using strobe CART0) FE4D C9 FB CMP #$FB ; First byte has two's complement of number of blocks in first frame FE4F 90 4C BCC $FE9D ; If value is less than #$FB it is not a correct header FE51 85 07 STA $07 ; Save block count FE53 9C AF FD STZ $FDAF ; GREENF = 0 FE56 9C BF FD STZ $FDBF ; BLUEREDF = 0 ; Read entire block of encrypted data (reversed) FE59 A2 32 LDX #$32 ; Each block in a frame is 50 + 1 byte long FE5B AD B2 FC LDA $FCB2 ; Read cart (using strobe CART0) FE5E 95 AA STA $AA,x ; Store block data in ZP from $AA to $DC FE60 CA DEX FE61 10 F8 BPL $FE5B ; Next byte ; Decrypt current block FE63 05 AB ORA $AB ; Accumulator contains last byte of encrypted data FE65 05 AC ORA $AC FE67 F0 34 BEQ $FE9D ; First three bytes should contain at least one non-zero value, otherwise error FE69 A2 02 LDX #$02 ; Start at position 2 FE6B B5 AA LDA $AA,x ; Read value FE6D FD 9A FF SBC $FF9A,x ; Subtract with public part of encryption key FE70 CA DEX FE71 10 F8 BPL $FE6B ; Next value FE73 B0 28 BCS $FE9D ; If sanity check fails go to error FE75 20 00 50 JSR $5000 ; Start decryption (plaintext = encrypted ^ public_exponent % modulus) FE78 B2 0B LDA ($0B) ; Sanity check for contents of decrypted (and still obfuscated) data's first byte FE7A C9 15 CMP #$15 ; Must always be $15 (do not know why) FE7C D0 1F BNE $FE9D ; On error FE7E A5 02 LDA $02 ; Get transition byte between blocks FE80 A0 32 LDY #$32 ; Start at end of decrypted data FE82 18 CLC FE83 71 0B ADC ($0B),y ; A += decrypted data FE85 92 05 STA ($05) ; Store at destination address ($0200) and onwards FE87 E6 05 INC $05 ; Update destination FE89 88 DEY ; Next byte FE8A D0 F6 BNE $FE82 ; Until all data in block is done FE8C 85 02 STA $02 ; Remember last byte FE8E E6 07 INC $07 ; Next block FE90 D0 C7 BNE $FE59 ; Start reading data from cart FE92 A0 02 LDY #$02 FE94 8C 8B FD STY $FD8B ; Set cart address data (IODAT) FE97 AA TAX FE98 D0 03 BNE $FE9D ; On error retry FE9A 4C 00 02 JMP $0200 ; Run whatever is at $0200 ; Retry lots of times FE9D E6 03 INC $03 FE9F D0 09 BNE $FEAA FEA1 E6 04 INC $04 FEA3 D0 05 BNE $FEAA FEA5 9C 87 FD STZ $FD87 ; Reset cart address counter FEA8 80 FE BRA $FEA8 ; Give up and loop forever ; Initialize Suzy FEAA A2 08 LDX #$08 FEAC BD EF FF LDA $FFEF,x FEAF BC E6 FF LDY $FFE6,x FEB2 99 00 FC STA $FC00,y FEB5 CA DEX FEB6 10 F4 BPL $FEAC FEB8 9C 91 FD STZ $FD91 ; Reset CPU bus request flip flop (draw INSERT GAME sprite) FEBB 9C 90 FD STZ $FD90 ; Clear SDONEACK FEBE 4C 26 FE JMP $FE26 ; Call routine to initialize Mikey ; Start of 256 byte area that is copied to $5000 FEC1 A9 11 LDA #$11 FEC3 85 0B STA $0B ; Pointer to start of zero page temporary data FEC5 A9 44 LDA #$44 ; End of working data zero page address FEC7 8D 71 50 STA $5071 ; Self-modifying code (see FF32) FECA A9 AA LDA #$AA FECC 85 0F STA $0F ; Pointer to start of encrypted data in zero page range FECE 20 18 50 JSR $5018 ; Currently FED9: perform first multiplication of enc * enc * enc (encrypted^3) FED1 A5 0B LDA $0B ; Transfer pointer to data with enc^2 ... FED3 85 0F STA $0F ; ... to encryption data FED5 A9 77 LDA #$77 ; Set pointer for work data right after squared encryption data FED7 85 0B STA $0B ; Perform second multiplication of enc * enc * enc (encrypted^3) ; Clear all 50 values of temporary work data FED9 A0 32 LDY #$32 FEDB A9 00 LDA #$00 FEDD 91 0B STA ($0B),y FEDF 88 DEY FEE0 10 FB BPL $FEDD ; Montgomery multiplication algorithm FEE2 C8 INY ; Y = 0 FEE3 B1 0F LDA ($0F),y ; Load value from encrypted data (or enc^2 on second pass) FEE5 85 0A STA $0A ; Store in work variable FEE7 C6 08 DEC $08 ; FEE9 A5 0B LDA $0B ; Change data address to work on FEEB 8D 37 50 STA $5037 ; Self-modifying code (see FEF7) FEEE 8D 43 50 STA $5043 ; Self-modifying code (see FF04) FEF1 8D 47 50 STA $5047 ; Self-modifying code (see FF08) FEF4 18 CLC FEF5 A2 32 LDX #$32 FEF7 36 00 ROL $00,x ; Operand corresponds to $5037 FEF9 CA DEX FEFA 10 FB BPL $FEF7 FEFC 06 0A ASL $0A FEFE 90 11 BCC $FF11 FF00 A2 32 LDX #$32 FF02 18 CLC FF03 B5 00 LDA $00,x ; Operand corresponds to $5043 FF05 75 AA ADC $AA,x FF07 95 00 STA $00,x ; Operand corresponds to $5047 FF09 CA DEX FF0A 10 F7 BPL $FF03 FF0C 20 5D 50 JSR $505D ; Call routine X FF0F 90 03 BCC $FF14 FF11 20 5D 50 JSR $505D ; Call routine X FF14 46 08 LSR $08 FF16 D0 D1 BNE $FEE9 FF18 C8 INY FF19 C0 33 CPY #$33 FF1B 90 C6 BCC $FEE3 FF1D 60 RTS ; $11 to $76 contains enc^2 at first pass ; Routine X (unknown purpose: Step in montgomery multiplication process?) FF1E A5 0B LDA $0B FF20 8D 6C 50 STA $506C FF23 B2 0B LDA ($0B) FF25 CD 9A FF CMP $FF9A ; Start of public part of encryption key FF28 90 18 BCC $FF42 ; Done FF2A A2 32 LDX #$32 FF2C B5 00 LDA $00,x FF2E FD 9A FF SBC $FF9A,x ; Subtract from corresponding byte in public key FF31 95 00 STA $00,x ; Operand corresponds to $5071 when copied FF33 CA DEX FF34 10 F6 BPL $FF2C FF36 90 0A BCC $FF42 ; Done FF38 A5 0B LDA $0B FF3A AE 71 50 LDX $5071 FF3D 86 0B STX $0B ; Update pointer to work data FF3F 8D 71 50 STA $5071 ; Self-modify code FF42 60 RTS ; SCB data for INSERT GAME sprite FF43 05 ; SPRCTLO (1 bit/pixel, no flipping, non-collideable sprite) FF44 93 ; SPRCTL1 (Totally literal, HSIZE and VSIZE specified, drawing starts at upper left quadrant) FF45 00 ; SPRCOLL FF46 00 00 .dw $0000 ; Address of next SCB FF48 92 50 .dw $5092 ; Address of SPRDLINE (sprite data) FF4A 80 00 .dw $0080 ; HPOSSTRT FF4C 48 00 .dw $0048 ; VPOSSTRT FF4E 00 04 .dw $0400 ; HSIZE (magnify by 4 horizontally) FF50 00 04 .dw $0400 ; VSIZE (magnify by 4 vertically) ; Palette colors FF52 F0 04 E2 EA 87 04 FA AA ; Sprite data (INSERT GAME upside down) FF53 04 E2 EA 87 ; ...000.0...0.0.0.0000... FF57 04 FA AA B7 ; .....0.0.0.0.0.0.0..0... FF5B 04 F2 08 97 ; ....00.00000.000.00.0... FF5F 04 FA 4A F7 ; .....0.00.00.0.0....0... FF63 04 E2 E8 87 ; ...000.0...0.000.0000... FF67 02 FF ; ........ FF69 05 B5 11 68 FF ; .0..0.0.000.000.0..0.000 .... .... FF6E 04 B5 D7 2D ; .0..0.0...0.0...00.0..0. FF72 04 B9 91 0D ; .0...00..00.000.0000..0. FF76 04 B5 DD 4D ; .0..0.0...0...0.0.00..0. FF7A 05 19 11 68 FF ; 000..00.000.000.0..0.000........ FF7F 00 .db $00 ; End of quadrant ; Start of boot sequence FF80 AD 88 FC LDA $FC88 ; Load SUZYHREV hardware version (always 1.0 for hardware) FF83 F0 7B BEQ $0000 FF85 68 PLA FF86 68 PLA FF87 68 PLA FF88 68 PLA FF89 A0 02 LDY #$02 FF8B 8C 8B FD STY $FD8B ; Set cartridge power off FF8E C8 INY FF8F 8C 8A FD STY $FD8A ; External power and cart address to output FF92 8C F9 FF STY $FFF9 ; Memory map to 03 (Mikey and ROM addresses are RAM) FF95 64 00 STZ $00 ; Set $0000 to 0 FF97 4C 19 FE JMP $FE19 ; Public key for decryption FF9A 35 B5 A3 94 28 06 D8 A2 FFA2 26 95 D7 71 B2 3C FD 56 FFAA 1C 4A 19 B6 A3 B0 26 00 FFB2 36 5A 30 6E 3C 4D 63 38 FFBA 1B D4 1C 13 64 89 36 4C FFC2 F2 BA 2A 58 F4 FE E1 FD FFCA AC 7E 79 ; Mikey addresses to be initialized (add to Mikey range offset $FD00) FFCD 90 .db $90 ; SDONEACK FFCE 92 .db $92 ; DISPCTL FFCF 95 .db $95 ; DISPADRH FFD0 94 .db $94 ; DISPADRL FFD1 93 .db $93 ; PBCKUP FFD2 09 .db $09 ; TIM2CTLA FFD3 08 .db $08 ; TIM2BCKUP FFD4 BF .db $BF ; BLUEREDF FFD5 AF .db $AF ; GREENF FFD6 B0 .db $B0 ; BLUERED0 FFD7 A0 .db $A0 ; GREEN0 FFD8 01 .db $01 ; TIM0CTLA FFD9 00 .db $00 ; TIM0BCKUP (also used as initialization value for SDONEACK) ; Initialization values for Mikey addresses (see also $FFCC) FFD9 00 .db $00 ; Render sprite command (and also address TIM0BCKUP) FFDA 0D .db $0D ; 4 bit color with video DMA enabled FFDB 20 .db $20 ; Video address at $2000 FFDC 00 .db $00 ; Video address at $2000 FFDD 29 .db $29 ; Magic P value for screen frequency FFDE 1F .db $1F ; Enable count and reload, linking FFDF 68 .db $68 ; 104 backup value for vertical scan timer (== 102 vertical lines plus 2) FFE0 3E .db $3E ; Yellow FFE1 0E .db $0E ; Yellow FFE2 00 .db $00 ; Black FFE3 00 .db $00 ; Black FFE4 18 .db $18 ; 2 microseconds timing for horizontal line FFE5 9E .db $9E ; 158 backup value for horizontal line scan (160 pixel across) ; Suzy addresses to be initialized (add to Suzy range offset $FC00) FFE6 91 .db $91 ; SPRGO FFE7 11 .db $11 ; SCBNEXTH FFE8 10 .db $10 ; SCBNEXTL FFE8 09 .db $09 ; VIDBASH FFEA 08 .db $08 ; VIDBASL FFEB 06 .db $06 ; VOFFL FFEC 04 .db $04 ; HOFFL FFED 90 .db $90 ; SUZYBUSEN FFEE 92 .db $92 ; SPRSYS ; Initialization values for Suzy addresses (see also $FFE6) FFEF 01 .db $01 ; Draw sprite (no everon detection) FFF0 50 .db $50 ; SCBNEXT = $5082 FFF1 82 .db $82 ; FFF2 20 .db $20 ; VIDBAS = $2000 FFF3 00 .db $00 ; FFF4 00 .db $00 ; VOFF = $0000 FFF5 00 .db $00 ; HOFF = $0000 FFF6 01 .db $01 ; Bus enabled FFF6 00 .db $00 ; Unsigned math, no accumulation, collission on, normal handed ; Reserved registers FFF8 00 .db $00 ; FFF9 80 ; MEMCTL value FFFA 00 30 ; NMI vector FFFC 80 FF ; Boot vector FFFE 80 FF ; IRQ vector 2 Quote Link to comment Share on other sites More sharing options...
LX.NET Posted December 28, 2011 Author Share Posted December 28, 2011 For those of you who want to take a look at a properly indented file, I have attached the file here. Annotated Lynx BootRom 0.2.txt 2 Quote Link to comment Share on other sites More sharing options...
sage Posted December 28, 2011 Share Posted December 28, 2011 Which disassbler do you use? Quote Link to comment Share on other sites More sharing options...
+Gemintronic Posted December 28, 2011 Share Posted December 28, 2011 Could this be the first step to a clean, open Lynx boot ROM? Or, does this not help in reverse engineering one iota? Quote Link to comment Share on other sites More sharing options...
LX.NET Posted December 28, 2011 Author Share Posted December 28, 2011 I guess we're already past the stage of open and clean Lynx boot ROMs, since Wookie has created a micro loader that is the bare minimum to load up a Lynx game (no splashscreen, e.g.). Check his work here: http://www.classicgamedev.com/Lynx:Lynx_Bootloader_Encryption_and_Decryption http://www.classicgamedev.com/Lynx:Obfuscate http://www.classicgamedev.com/Blog:Hacking_Classics/Micro_Loader Quote Link to comment Share on other sites More sharing options...
sage Posted December 28, 2011 Share Posted December 28, 2011 you misunderstood the question. he asks for a lynxboot.img replacement. Quote Link to comment Share on other sites More sharing options...
ThomH Posted December 28, 2011 Share Posted December 28, 2011 I would imagine that a lynxboot.img replacement could be written but more likely the functionality will just be built directly into the relevant emulators? Quote Link to comment Share on other sites More sharing options...
sage Posted December 29, 2011 Share Posted December 29, 2011 make a md5sum for each rom and depending on that read the dir at 410, 512 or 896. yes, no problem. Quote Link to comment Share on other sites More sharing options...
ThomH Posted December 29, 2011 Share Posted December 29, 2011 make a md5sum for each rom and depending on that read the dir at 410, 512 or 896. yes, no problem. Why even bother with that? Just reimplement the algorithm as given above, giving compatibility with every existing ROM, every prototype yet to be rediscovered and every homebrew yet to be written. Quote Link to comment Share on other sites More sharing options...
+karri Posted December 29, 2011 Share Posted December 29, 2011 (edited) make a md5sum for each rom and depending on that read the dir at 410, 512 or 896. yes, no problem. There is no guarantee of where the directory is or what it looks like. The current cc65 bootloader is 52+171 = 223 bytes. And after that the dir starts. My dir happens to be 8 bytes per entry in the same format as usual. But Wookie had plans to skip the directory completely and use some kind of streaming data structures. The correct way to find out what happens at boot time is to analyze the first byte. If it is FF then the first entry is one block. If it is FE then it is two blocks etc. Then you need to skip over as many blocks as there were. The next thing is to repeat the process again. There will be some byte like FC for telling how many blocks we have at this time. So the possibilities are: FF + 51 bytes = 52 bytes (my bootloader at cc65.org) FE + (2 * 51) + FA + (6 * 51) = 410 bytes (Harry Dodgson's bootloader) Here is my bootloaders source. .segment "BOOTLDR" ;********************************** ; Here is the bootloader in plaintext ; The idea is to make the smallest possible encrypted loader as decryption ; is very slow. The minimum size is 49 bytes plus a zero byte. ;********************************** ; EXE = $f000 ; ; .org $0200 ; ; ; 1. force Mikey to be in memory ; stz MAPCTL ; ; ; 3. set ComLynx to open collector ; lda #4 ; a = 00000100 ; sta SERCTL ; set the ComLynx to open collector ; ; ; 4. make sure the ROM is powered on ; lda #8 ; a = 00001000 ; sta IODAT ; set the ROM power to on ; ; ; 5. read in secondary exe + 8 bytes (1st directory entry) from the cart and store it in $f000 ; ldx #0 ; x = 0 ; ldy #$AB ; y = secondary loader size + 1st directory entry (171 bytes) ;rloop1: lda RCART0 ; read a byte from the cart ; sta EXE,X ; EXE[X] = a ; inx ; x++ ; dey ; y-- ; bne rloop1 ; loops until y wraps ; ; ; 6. jump to secondary loader ; jmp EXE ; run the secondary loader ; ; ; plus lots of zero bytes as there is empty space to fill the 49 bytes of code ; ; .reloc ;********************************** ; After compilation, encryption and obfuscation it turns into this. ;********************************** .byte $ff, $dc, $e3, $bd, $bc, $7f, $f8, $94 .byte $b7, $dd, $68, $bb, $da, $5b, $50, $5c .byte $ea, $9f, $2b, $df, $96, $80, $3f, $7e .byte $ef, $15, $81, $ae, $ad, $e4, $6e, $b3 .byte $46, $d7, $72, $58, $f7, $76, $8a, $4a .byte $c7, $99, $bd, $ff, $02, $3e, $5b, $3f .byte $0c, $49, $1b, $22 This bootloader does NOT need to know what kind of cart it resides on. It is so small that it can always load the secondary bootloader without a need to change blocks. Then the secondary bootloader can be compiled to have the correct understanding of blocksizes. So this is different for different carts. Cool But as you see there is no reason why the bootloader needs a directory or a title sprite or anything else. -- Karri Edited December 29, 2011 by karri 1 Quote Link to comment Share on other sites More sharing options...
sage Posted January 11, 2012 Share Posted January 11, 2012 (edited) Just to let you know. I successfully did a lynxboot.img replacement based only on "free" sources, without reverse engenieering the original ROM (which is the major point to get it "free"). It might not work for all future ROMs but for what I tried until now it is working (used in mednafen). There are still two issues which I would like to solve before publishing esp I need to test more ROMs. Edited January 11, 2012 by sage Quote Link to comment Share on other sites More sharing options...
ThomH Posted January 14, 2012 Share Posted January 14, 2012 I successfully did a lynxboot.img replacement based only on "free" sources, without reverse engenieering the original ROM (which is the major point to get it "free"). Fantastic work! So it's a new drop-in ROM replacement for any old emulator, that I could in theory use on original hardware? From a legal point of view, you're contaminated by having posted on this thread before producing your ROM, given that the original source code is listed right there at the top. Except that Hasbro's release of development rights into the public domain (which there are lots of second hand sources for, but I can't seem to find the original announcement) definitely means that nobody's ever actually going to sue. It might not work for all future ROMs but for what I tried until now it is working (used in mednafen). There are still two issues which I would like to solve before publishing esp I need to test more ROMs. I'm on-and-off working on a new development environment, in which you write whatever code you want for the encrypted bootstrap. If you wanted to go with the serial media metaphor then that'd be your own business, as I certainly won't be going to any effort to support it. I'm probably still a few weeks away from having any working ROMs but would it be helpful to send some over with a variety of boot schemes when the moment comes? Quote Link to comment Share on other sites More sharing options...
sage Posted January 14, 2012 Share Posted January 14, 2012 I successfully did a lynxboot.img replacement based only on "free" sources, without reverse engenieering the original ROM (which is the major point to get it "free"). Fantastic work! So it's a new drop-in ROM replacement for any old emulator, that I could in theory use on original hardware? if you replace your main cpu, yes. From a legal point of view, you're contaminated by having posted on this thread before producing your ROM, given that the original source code is listed right there at the top. Except that Hasbro's release of development rights into the public domain (which there are lots of second hand sources for, but I can't seem to find the original announcement) definitely means that nobody's ever actually going to sue. As you have actually no idea what and how I do it, I do not see how you can come to that conclusion. It might not work for all future ROMs but for what I tried until now it is working (used in mednafen). There are still two issues which I would like to solve before publishing esp I need to test more ROMs. I'm on-and-off working on a new development environment, in which you write whatever code you want for the encrypted bootstrap. If you wanted to go with the serial media metaphor then that'd be your own business, as I certainly won't be going to any effort to support it. I'm probably still a few weeks away from having any working ROMs but would it be helpful to send some over with a variety of boot schemes when the moment comes? I do not understand your argument. Quote Link to comment Share on other sites More sharing options...
ThomH Posted January 14, 2012 Share Posted January 14, 2012 As you have actually no idea what and how I do it, I do not see how you can come to that conclusion. You have seen the original source code before reimplementing it. That's the legal test. I do not understand your argument. There is no argument there. It's a question. Quote Link to comment Share on other sites More sharing options...
sage Posted January 14, 2012 Share Posted January 14, 2012 I'm on-and-off working on a new development environment, in which you write whatever code you want for the encrypted bootstrap. If you wanted to go with the serial media metaphor then that'd be your own business, as I certainly won't be going to any effort to support it. I still dont understand neither your remark nor I'm probably still a few weeks away from having any working ROMs but would it be helpful to send some over with a variety of boot schemes when the moment comes? .. what you want to tell me with this sentence. I have no idea what your variety of "boot schemes" might be. Quote Link to comment Share on other sites More sharing options...
ThomH Posted January 14, 2012 Share Posted January 14, 2012 (edited) I still dont understand neither your remark nor See clean room design. Your design is not a clean room design. It is therefore not clean in the software sense. Hence its redistribution is of questionable legality — see Sony v Connectix, which I think is exactly applicable here as it covers a situation where the original ROM was disassembled in order to reproduce its functionality without copying its code. The original court decided for Sony, the first appeal court decided for Connectix and then Sony bought Connectix before it could get out of the US district courts. Which sadly leaves us without a pronouncement from a reliably senior court. .. what you want to tell me with this sentence. I have no idea what your variety of "boot schemes" might be. It means you write whatever code you want in the encrypted part of the cartridge (usually referred to as the boot loader, such as here or here) and then whatever code you want in the unencrypted part. Do whatever you want. Epyx's questionable decision to pretend there was a filing system is of no relevance. You could follow Harry Dodson's route and just load some data immediately after the encrypted portion and hop into it. You could use an Epyx-style filing system. You could use absolutely any other filing system, whether FAT16, ADFS, BetaDOS or whatever. You could encrypt your entire program code, if it's small. EDIT: I think my tone has become too negative, so I'm editing to repeat that it's fantastic that you expend your time and energy into Mednafen and into other Lynx emulation improvement tasks such as this one. From what I can see, you do good work and are a benefit to the community, and I'm sure I'm not the only one that wants to say thanks. Edited January 14, 2012 by ThomH Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.