643 lines
17 KiB
C
643 lines
17 KiB
C
#include "snes.h"
|
|
|
|
//=================================================================================================
|
|
//
|
|
// SNES operations
|
|
// This file includes all the snes functions possible to be called from the snes dictionary.
|
|
//
|
|
// See description of the commands contained here in shared/shared_dictionaries.h
|
|
//
|
|
//=================================================================================================
|
|
|
|
/* Desc:Function takes an opcode which was transmitted via USB
|
|
* then decodes it to call designated function.
|
|
* shared_dict_snes.h is used in both host and fw to ensure opcodes/names align
|
|
* Pre: Macros must be defined in firmware pinport.h
|
|
* opcode must be defined in shared_dict_snes.h
|
|
* Post:function call complete.
|
|
* Rtn: SUCCESS if opcode found and completed, error if opcode not present or other problem.
|
|
*/
|
|
uint8_t snes_call( uint8_t opcode, uint8_t miscdata, uint16_t operand, uint8_t *rdata )
|
|
{
|
|
|
|
#define RD_LEN 0
|
|
#define RD0 1
|
|
#define RD1 2
|
|
|
|
#define BYTE_LEN 1
|
|
#define HWORD_LEN 2
|
|
|
|
switch (opcode) {
|
|
//no return value:
|
|
case SNES_SET_BANK:
|
|
HADDR_SET( operand );
|
|
break;
|
|
case SNES_ROM_WR:
|
|
snes_rom_wr( operand, miscdata );
|
|
break;
|
|
|
|
//8bit return values:
|
|
case SNES_ROM_RD:
|
|
rdata[RD_LEN] = BYTE_LEN;
|
|
rdata[RD0] = snes_rom_rd( operand );
|
|
break;
|
|
default:
|
|
//macro doesn't exist
|
|
return ERR_UNKN_SNES_OPCODE;
|
|
}
|
|
|
|
return SUCCESS;
|
|
|
|
}
|
|
|
|
/* Desc:SNES ROM Read without changing high bank
|
|
* /ROMSEL always set low
|
|
* EXP0/RESET not affected
|
|
* Pre: snes_init() setup of io pins
|
|
* Post:address left on bus
|
|
* data bus left clear
|
|
* Rtn: Byte read from ROM at addr
|
|
*/
|
|
uint8_t snes_rom_rd( uint16_t addr )
|
|
{
|
|
uint8_t read; //return value
|
|
|
|
//set address bus
|
|
ADDR_SET(addr);
|
|
|
|
ROMSEL_LO();
|
|
CSRD_LO();
|
|
|
|
//couple more NOP's waiting for data
|
|
//zero nop's returned previous databus value
|
|
NOP(); //one nop got most of the bits right
|
|
NOP(); //two nop got all the bits right
|
|
NOP(); //add third nop for some extra
|
|
NOP(); //one more can't hurt
|
|
//might need to wait longer for some carts...
|
|
|
|
//latch data
|
|
DATA_RD(read);
|
|
|
|
//return bus to default
|
|
CSRD_HI();
|
|
ROMSEL_HI();
|
|
|
|
return read;
|
|
}
|
|
|
|
/* Desc:SNES ROM Write
|
|
* /ROMSEL always set low
|
|
* EXP0/RESET unaffected
|
|
* write value to currently selected bank
|
|
* Pre: snes_init() setup of io pins
|
|
* Post:data latched by anything listening on the bus
|
|
* address left on bus
|
|
* Rtn: None
|
|
*/
|
|
void snes_rom_wr( uint16_t addr, uint8_t data )
|
|
{
|
|
|
|
ADDR_SET(addr);
|
|
|
|
//put data on bus
|
|
DATA_OP();
|
|
DATA_SET(data);
|
|
|
|
//set /WR low first as this sets direction of
|
|
//level shifter on v3.0 boards
|
|
CSWR_LO();
|
|
//Then set romsel as this enables output of level shifter
|
|
ROMSEL_LO();
|
|
//Doing the other order creates bus conflict between ROMSEL low -> WR low
|
|
|
|
//give some time
|
|
NOP();
|
|
NOP();
|
|
NOP(); //3x total NOPs fails ~2Bytes per 2MByte on v3.0 proto and inl6
|
|
//swaping /WR /ROMSEL order above helped greatly
|
|
//but still had 2 byte fails adding NOPS
|
|
NOP(); //4x total NOPs passed all bytes v3.0 SNES and inl6
|
|
//NOP();
|
|
//NOP(); //6x total NOPs passed all bytes
|
|
|
|
|
|
//latch data to cart memory/mapper
|
|
CSWR_HI();
|
|
ROMSEL_HI();
|
|
|
|
//Free data bus
|
|
DATA_IP();
|
|
}
|
|
|
|
/* Desc:SNES ROM Write to current address
|
|
* /ROMSEL always set low
|
|
* EXP0/RESET unaffected
|
|
* write value to currently selected bank, and current address
|
|
* Mostly used when address is don't care
|
|
* Pre: snes_init() setup of io pins
|
|
* Post:data latched by anything listening on the bus
|
|
* address left on bus
|
|
* Rtn: None
|
|
*/
|
|
void snes_rom_wr_cur_addr( uint8_t data )
|
|
{
|
|
|
|
// ADDR_SET(addr);
|
|
|
|
//put data on bus
|
|
DATA_OP();
|
|
DATA_SET(data);
|
|
|
|
//set /WR low first as this sets direction of
|
|
//level shifter on v3.0 boards
|
|
CSWR_LO();
|
|
//Then set romsel as this enables output of level shifter
|
|
ROMSEL_LO();
|
|
//Doing the other order creates bus conflict between ROMSEL low -> WR low
|
|
|
|
//give some time
|
|
NOP();
|
|
NOP();
|
|
NOP(); //3x total NOPs fails ~2Bytes per 2MByte on v3.0 proto and inl6
|
|
//swaping /WR /ROMSEL order above helped greatly
|
|
//but still had 2 byte fails adding NOPS
|
|
NOP(); //4x total NOPs passed all bytes v3.0 SNES and inl6
|
|
//NOP();
|
|
//NOP(); //6x total NOPs passed all bytes
|
|
|
|
//latch data to cart memory/mapper
|
|
CSWR_HI();
|
|
ROMSEL_HI();
|
|
|
|
//Free data bus
|
|
DATA_IP();
|
|
}
|
|
/* Desc:SNES ROM Page Read with optional USB polling
|
|
* /ROMSEL always low, EXP0/RESET unaffected
|
|
* if poll is true calls usbdrv.h usbPoll fuction
|
|
* this is needed to keep from timing out when double buffering usb data
|
|
* Pre: snes_init() setup of io pins
|
|
* num_bytes can't exceed 256B page boundary
|
|
* Post:address left on bus
|
|
* data bus left clear
|
|
* data buffer filled starting at first to last
|
|
* Rtn: Index of last byte read
|
|
*/
|
|
uint8_t snes_rom_page_rd_poll( uint8_t *data, uint8_t addrH, uint8_t first, uint8_t len, uint8_t poll )
|
|
{
|
|
uint8_t i;
|
|
|
|
//set address bus
|
|
ADDRH(addrH);
|
|
|
|
//set /ROMSEL and /RD
|
|
CSRD_LO();
|
|
ROMSEL_LO();
|
|
|
|
//set lower address bits
|
|
ADDRL(first); //doing this prior to entry and right after latching
|
|
//gives longest delay between address out and latching data
|
|
for( i=0; i<=len; i++ ) {
|
|
//testing shows that having this if statement doesn't affect overall dumping speed
|
|
if ( poll == FALSE ) {
|
|
NOP(); //couple more NOP's waiting for data
|
|
NOP(); //one prob good enough considering the if/else
|
|
NOP();
|
|
NOP();
|
|
} else {
|
|
usbPoll(); //Call usbdrv.h usb polling while waiting for data
|
|
NOP();
|
|
NOP();
|
|
NOP();
|
|
}
|
|
//latch data
|
|
DATA_RD(data[i]);
|
|
|
|
//set lower address bits
|
|
//ADDRL(++first); THIS broke things, on stm adapter because macro expands it twice!
|
|
first++;
|
|
ADDRL(first);
|
|
}
|
|
|
|
//return bus to default
|
|
CSRD_HI();
|
|
ROMSEL_HI();
|
|
|
|
//return index of last byte read
|
|
return i;
|
|
}
|
|
|
|
|
|
// /* Desc: Discrete board PRG-ROM only write, does not write to mapper
|
|
// * PRG-ROM /WE <- EXP0 w/PU
|
|
// * PRG-ROM /OE <- /ROMSEL
|
|
// * PRG-ROM /CE <- GND
|
|
// * PRG-ROM write: /WE & /CE low, /OE high
|
|
// * mapper '161 CLK <- /ROMSEL
|
|
// * mapper '161 /LOAD <- PRG R/W
|
|
// * mapper '161 /LOAD must be low on rising edge of CLK to latch data
|
|
// * This is a /WE controlled write. Address latched on falling edge,
|
|
// * and data latched on rising edge EXP0
|
|
// * Note:addrH bit7 has no effect (ends up on PPU /A13)
|
|
// * /ROMSEL, M2, & PRG R/W signals untouched
|
|
// * Pre: nes_init() setup of io pins
|
|
// * Post:data latched by PRG-ROM, mapper register unaffected
|
|
// * address left on bus
|
|
// * data left on bus, but pullup only
|
|
// * EXP0 left pulled up
|
|
// * Rtn: None
|
|
// */
|
|
// void discrete_exp0_prgrom_wr( uint16_t addr, uint8_t data )
|
|
// {
|
|
// ADDR_SET(addr);
|
|
//
|
|
// DATA_OP();
|
|
// DATA_SET(data);
|
|
//
|
|
// EXP0_OP(); //Tas = 0ns, Tah = 30ns
|
|
// EXP0_LO();
|
|
// EXP0_IP_PU(); //Twp = 40ns, Tds = 40ns, Tdh = 0ns
|
|
// //16Mhz avr clk = 62.5ns period guarantees timing reqts
|
|
// DATA_IP();
|
|
// }
|
|
//
|
|
// //
|
|
// // /* Desc:Emulate NES CPU Read as best possible
|
|
// // * decode A15 from addrH to set /ROMSEL as expected
|
|
// // * float EXP0
|
|
// // * toggle M2 as NES would
|
|
// // * insert some NOP's in to be slow like NES
|
|
// // * Note:not the fastest read operation
|
|
// // * Pre: nes_init() setup of io pins
|
|
// // * Post:address left on bus
|
|
// // * data bus left clear
|
|
// // * EXP0 left floating
|
|
// // * Rtn: Byte read from PRG-ROM at addrHL
|
|
// // */
|
|
// // uint8_t emulate_nes_cpu_rd( uint8_t addrH, uint8_t addrL )
|
|
// // {
|
|
// // uint8_t read; //return value
|
|
// //
|
|
// // //m2 should be low as it aids in disabling WRAM
|
|
// // //this is also m2 state at beginging of CPU cycle
|
|
// // //all these pins should already be in this state, but
|
|
// // //go ahead and setup just to be sure since we're trying
|
|
// // //to be as accurate as possible
|
|
// // _EXP0_FLT(); //this could have been left pulled up
|
|
// // _M2_LO(); //start of CPU cycle
|
|
// // _ROMSEL_HI(); //trails M2
|
|
// // _PRGRW_RD(); //happens just after M2
|
|
// //
|
|
// // //set address bus
|
|
// // ADDR_OUT = addrL;
|
|
// // _ADDRH_SET(addrH);
|
|
// //
|
|
// // //couple NOP's to wait a bit
|
|
// // NOP();
|
|
// // NOP();
|
|
// //
|
|
// // //set M2 and /ROMSEL
|
|
// // if( addrH >= 0x80 ) { //addressing cart rom space
|
|
// // _M2_HI();
|
|
// // _ROMSEL_LO(); //romsel trails M2 during CPU operations
|
|
// // } else {
|
|
// // _M2_HI();
|
|
// // }
|
|
// //
|
|
// // //couple more NOP's waiting for data
|
|
// // NOP();
|
|
// // NOP();
|
|
// // NOP();
|
|
// // NOP();
|
|
// // NOP();
|
|
// // NOP();
|
|
// //
|
|
// // //latch data
|
|
// // read = DATA_IN;
|
|
// //
|
|
// // //return bus to default
|
|
// // _M2_LO();
|
|
// // _ROMSEL_HI();
|
|
// //
|
|
// // return read;
|
|
// // }
|
|
// //
|
|
// /* Desc:NES CPU Read without being so slow
|
|
// * decode A15 from addrH to set /ROMSEL as expected
|
|
// * float EXP0
|
|
// * toggle M2 as NES would
|
|
// * Pre: nes_init() setup of io pins
|
|
// * Post:address left on bus
|
|
// * data bus left clear
|
|
// * EXP0 left floating
|
|
// * Rtn: Byte read from PRG-ROM at addrHL
|
|
// */
|
|
// uint8_t nes_cpu_rd( uint16_t addr )
|
|
// {
|
|
// uint8_t read; //return value
|
|
//
|
|
// //set address bus
|
|
// ADDR_SET(addr);
|
|
//
|
|
// //set M2 and /ROMSEL
|
|
// MCO_HI();
|
|
// if( addr >= 0x8000 ) { //addressing cart rom space
|
|
// ROMSEL_LO(); //romsel trails M2 during CPU operations
|
|
// }
|
|
//
|
|
// //couple more NOP's waiting for data
|
|
// //zero nop's returned previous databus value
|
|
// NOP(); //one nop got most of the bits right
|
|
// NOP(); //two nop got all the bits right
|
|
// NOP(); //add third nop for some extra
|
|
// NOP(); //one more can't hurt
|
|
// //might need to wait longer for some carts...
|
|
//
|
|
// //latch data
|
|
// DATA_RD(read);
|
|
//
|
|
// //return bus to default
|
|
// MCO_LO();
|
|
// ROMSEL_HI();
|
|
//
|
|
// return read;
|
|
// }
|
|
//
|
|
// /* Desc:NES CPU Write
|
|
// * Just as you would expect NES's CPU to perform
|
|
// * A15 decoded to enable /ROMSEL
|
|
// * This ends up as a M2 and/or /ROMSEL controlled write
|
|
// * Note:addrH bit7 has no effect (ends up on PPU /A13)
|
|
// * EXP0 floating
|
|
// * Pre: nes_init() setup of io pins
|
|
// * Post:data latched by anything listening on the bus
|
|
// * address left on bus
|
|
// * data left on bus, but pullup only
|
|
// * Rtn: None
|
|
// */
|
|
// void nes_cpu_wr( uint16_t addr, uint8_t data )
|
|
// {
|
|
// //Float EXP0 as it should be in NES
|
|
// EXP0_IP_FL();
|
|
//
|
|
// //need for whole function
|
|
// //_DATA_OP();
|
|
//
|
|
// //set addrL
|
|
// //ADDR_OUT = addrL;
|
|
// //latch addrH
|
|
// //DATA_OUT = addrH;
|
|
// //_AHL_CLK();
|
|
// ADDR_SET(addr);
|
|
//
|
|
// //PRG R/W LO
|
|
// PRGRW_LO();
|
|
//
|
|
// //put data on bus
|
|
// DATA_OP();
|
|
// DATA_SET(data);
|
|
//
|
|
// //set M2 and /ROMSEL
|
|
// MCO_HI();
|
|
// if( addr >= 0x8000 ) { //addressing cart rom space
|
|
// ROMSEL_LO(); //romsel trails M2 during CPU operations
|
|
// }
|
|
//
|
|
// //give some time
|
|
// NOP();
|
|
// NOP();
|
|
//
|
|
// //latch data to cart memory/mapper
|
|
// MCO_LO();
|
|
// ROMSEL_HI();
|
|
//
|
|
// //retore PRG R/W to default
|
|
// PRGRW_HI();
|
|
//
|
|
// //Free data bus
|
|
// DATA_IP();
|
|
// }
|
|
//
|
|
// /* Desc:NES PPU Read
|
|
// * decode A13 from addrH to set /A13 as expected
|
|
// * Pre: nes_init() setup of io pins
|
|
// * Post:address left on bus
|
|
// * data bus left clear
|
|
// * Rtn: Byte read from CHR-ROM/RAM at addrHL
|
|
// */
|
|
// uint8_t nes_ppu_rd( uint16_t addr )
|
|
// {
|
|
// uint8_t read; //return value
|
|
//
|
|
// //addr with PPU /A13
|
|
// if (addr < 0x2000) { //below $2000 A13 clear, /A13 set
|
|
// addr |= PPU_A13N_WORD;
|
|
// } //above PPU $1FFF, A13 set, /A13 clear
|
|
//
|
|
// ADDR_SET( addr );
|
|
//
|
|
// //set CHR /RD and /WR
|
|
// CSRD_LO();
|
|
//
|
|
// //couple more NOP's waiting for data
|
|
// //zero nop's returned previous databus value
|
|
// NOP(); //one nop got most of the bits right
|
|
// NOP(); //two nop got all the bits right
|
|
// NOP(); //add third nop for some extra
|
|
// NOP(); //one more can't hurt
|
|
// //might need to wait longer for some carts...
|
|
//
|
|
// //latch data
|
|
// DATA_RD(read);
|
|
//
|
|
// //return bus to default
|
|
// CSRD_HI();
|
|
//
|
|
// return read;
|
|
// }
|
|
//
|
|
// /* Desc:NES PPU Write
|
|
// * decode A13 from addrH to set /A13 as expected
|
|
// * flash: address clocked falling edge, data rising edge of /WE
|
|
// * Pre: nes_init() setup of io pins
|
|
// * Post:data written to addrHL
|
|
// * address left on bus
|
|
// * data bus left clear
|
|
// * Rtn: None
|
|
// */
|
|
//
|
|
// void nes_ppu_wr( uint16_t addr, uint8_t data )
|
|
// {
|
|
//
|
|
// //addr with PPU /A13
|
|
// if (addr < 0x2000) { //below $2000 A13 clear, /A13 set
|
|
// addr |= PPU_A13N_WORD;
|
|
// } //above PPU $1FFF, A13 set, /A13 clear
|
|
//
|
|
// ADDR_SET( addr );
|
|
//
|
|
// //put data on bus
|
|
// DATA_OP();
|
|
// DATA_SET(data);
|
|
//
|
|
// NOP();
|
|
//
|
|
// //set CHR /RD and /WR
|
|
// CSWR_LO();
|
|
//
|
|
// //might need to wait longer for some carts...
|
|
// NOP(); //one can't hurt
|
|
//
|
|
// //latch data to memory
|
|
// CSWR_HI();
|
|
//
|
|
// //clear data bus
|
|
// DATA_IP();
|
|
//
|
|
// }
|
|
//
|
|
//
|
|
// /* Desc:PPU CIRAM A10 NT arrangement sense
|
|
// * Toggle A11 and A10 and read back CIRAM A10
|
|
// * report back if vert/horiz/1scnA/1scnB
|
|
// * reports nesdev defined mirroring
|
|
// * does not report Nintendo's "Name Table Arrangement"
|
|
// * Pre: nes_init() setup of io pins
|
|
// * Post:address left on bus
|
|
// * Rtn: MIR_VERT, MIR_HORIZ, MIR_1SCNA, MIR_1SCNB
|
|
// * errors not really possible since all combinations
|
|
// * of CIRAM A10 level designate something valid
|
|
// */
|
|
// uint8_t ciram_a10_mirroring( void )
|
|
// {
|
|
// uint16_t readV, readH;
|
|
//
|
|
// //set A10, clear A11
|
|
// ADDRH(A10_BYTE);
|
|
// CIA10_RD(readV);
|
|
//
|
|
// //set A11, clear A10
|
|
// ADDRH(A11_BYTE);
|
|
// CIA10_RD(readH);
|
|
//
|
|
// //if CIRAM A10 was always low -> 1 screen A
|
|
// if ((readV==0) & (readH==0)) return MIR_1SCNA;
|
|
// //if CIRAM A10 was always hight -> 1screen B
|
|
// if ((readV!=0) & (readH!=0)) return MIR_1SCNB;
|
|
// //if CIRAM A10 toggled with A10 -> Vertical mirroring, horizontal arrangement
|
|
// if ((readV!=0) & (readH==0)) return MIR_VERT;
|
|
// //if CIRAM A10 toggled with A11 -> Horizontal mirroring, vertical arrangement
|
|
// if ((readV==0) & (readH!=0)) return MIR_HORZ;
|
|
//
|
|
// //shouldn't be here...
|
|
// return GEN_FAIL;
|
|
// }
|
|
//
|
|
// /* Desc:NES CPU Page Read with optional USB polling
|
|
// * decode A15 from addrH to set /ROMSEL as expected
|
|
// * float EXP0
|
|
// * toggle M2 as NES would
|
|
// * if poll is true calls usbdrv.h usbPoll fuction
|
|
// * this is needed to keep from timing out when double buffering usb data
|
|
// * Pre: nes_init() setup of io pins
|
|
// * num_bytes can't exceed 256B page boundary
|
|
// * Post:address left on bus
|
|
// * data bus left clear
|
|
// * EXP0 left floating
|
|
// * data buffer filled starting at first to last
|
|
// * Rtn: Index of last byte read
|
|
// */
|
|
// uint8_t nes_cpu_page_rd_poll( uint8_t *data, uint8_t addrH, uint8_t first, uint8_t len, uint8_t poll )
|
|
// {
|
|
// uint8_t i;
|
|
//
|
|
// //set address bus
|
|
// ADDRH(addrH);
|
|
//
|
|
// //set M2 and /ROMSEL
|
|
// MCO_HI();
|
|
// if( addrH >= 0x80 ) { //addressing cart rom space
|
|
// ROMSEL_LO(); //romsel trails M2 during CPU operations
|
|
// }
|
|
//
|
|
// //set lower address bits
|
|
// ADDRL(first); //doing this prior to entry and right after latching
|
|
// //gives longest delay between address out and latching data
|
|
// for( i=0; i<=len; i++ ) {
|
|
// //testing shows that having this if statement doesn't affect overall dumping speed
|
|
// if ( poll == FALSE ) {
|
|
// NOP(); //couple more NOP's waiting for data
|
|
// NOP(); //one prob good enough considering the if/else
|
|
// } else {
|
|
// usbPoll(); //Call usbdrv.h usb polling while waiting for data
|
|
// }
|
|
// //latch data
|
|
// DATA_RD(data[i]);
|
|
// //set lower address bits
|
|
// //ADDRL(++first); THIS broke things, on stm adapter because macro expands it twice!
|
|
// first++;
|
|
// ADDRL(first);
|
|
// }
|
|
//
|
|
// //return bus to default
|
|
// MCO_LO();
|
|
// ROMSEL_HI();
|
|
//
|
|
// //return index of last byte read
|
|
// return i;
|
|
// }
|
|
//
|
|
// /* Desc:NES PPU Page Read with optional USB polling
|
|
// * decode A13 from addrH to set /A13 as expected
|
|
// * if poll is true calls usbdrv.h usbPoll fuction
|
|
// * this is needed to keep from timing out when double buffering usb data
|
|
// * Pre: nes_init() setup of io pins
|
|
// * num_bytes can't exceed 256B page boundary
|
|
// * Post:address left on bus
|
|
// * data bus left clear
|
|
// * data buffer filled starting at first for len number of bytes
|
|
// * Rtn: Index of last byte read
|
|
// */
|
|
// uint8_t nes_ppu_page_rd_poll( uint8_t *data, uint8_t addrH, uint8_t first, uint8_t len, uint8_t poll )
|
|
// {
|
|
// uint8_t i;
|
|
//
|
|
// if (addrH < 0x20) { //below $2000 A13 clear, /A13 set
|
|
// //ADDRH(addrH | PPU_A13N_BYTE);
|
|
// //Don't do weird stuff like above! logic inside macro expansions can have weird effects!!
|
|
// addrH |= PPU_A13N_BYTE;
|
|
// ADDRH(addrH);
|
|
// } else { //above PPU $1FFF, A13 set, /A13 clear
|
|
// ADDRH(addrH);
|
|
// }
|
|
//
|
|
// //set CHR /RD and /WR
|
|
// CSRD_LO();
|
|
//
|
|
// //set lower address bits
|
|
// ADDRL(first); //doing this prior to entry and right after latching
|
|
// //gives longest delay between address out and latching data
|
|
//
|
|
// for( i=0; i<=len; i++ ) {
|
|
// //couple more NOP's waiting for data
|
|
// if ( poll == FALSE ) {
|
|
// NOP(); //one prob good enough considering the if/else
|
|
// NOP();
|
|
// } else {
|
|
// usbPoll();
|
|
// }
|
|
// //latch data
|
|
// DATA_RD(data[i]);
|
|
// //set lower address bits
|
|
// first ++;
|
|
// ADDRL(first);
|
|
// }
|
|
//
|
|
// //return bus to default
|
|
// CSRD_HI();
|
|
//
|
|
// //return index of last byte read
|
|
// return i;
|
|
// }
|