519 lines
16 KiB
C#
519 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using BizHawk.Common;
|
|
/**
|
|
This is the cartridge class for Arcadia (aka Starpath) Supercharger
|
|
games. Christopher Salomon provided most of the technical details
|
|
used in creating this class. A good description of the Supercharger
|
|
is provided in the Cuttle Cart's manual.
|
|
|
|
The Supercharger has four 2K banks. There are three banks of RAM
|
|
and one bank of ROM. All 6K of the RAM can be read and written.
|
|
|
|
D7-D5 of this byte: Write Pulse Delay (n/a for emulator)
|
|
|
|
D4-D0: RAM/ROM configuration:
|
|
$F000-F7FF $F800-FFFF Address range that banks map into
|
|
000wp 2 ROM
|
|
001wp 0 ROM
|
|
010wp 2 0 as used in Commie Mutants and many others
|
|
011wp 0 2 as used in Suicide Mission
|
|
100wp 2 ROM
|
|
101wp 1 ROM
|
|
110wp 2 1 as used in Killer Satellites
|
|
111wp 1 2 as we use for 2k/4k ROM cloning
|
|
|
|
w = Write Enable (1 = enabled; accesses to $F000-$F0FF cause writes
|
|
to happen. 0 = disabled, and the cart acts like ROM.)
|
|
p = ROM Power (0 = enabled, 1 = off.) Only power the ROM if you're
|
|
wanting to access the ROM for multiloads. Otherwise set to 1.
|
|
*/
|
|
namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
|
{
|
|
internal class mAR : MapperBase
|
|
{
|
|
public mAR(Atari2600 core)
|
|
{
|
|
Core = core;
|
|
InitializeSettings();
|
|
}
|
|
|
|
private ByteBuffer _superChargerImage = new ByteBuffer(8192);
|
|
private IntBuffer _imageOffsets = new IntBuffer(2);
|
|
private bool _writePending = false;
|
|
private int _distinctAccesses = 0;
|
|
private bool _writeEnabled = false;
|
|
private byte _dataHoldRegister;
|
|
private byte _numberOfLoadImages;
|
|
private ByteBuffer _loadedImages;
|
|
private ByteBuffer _header = new ByteBuffer(256);
|
|
private bool _powerIndicator; // Indicates if the ROM's power is on or off
|
|
private int _powerRomCycle; // Indicates when the power was last turned on
|
|
private int _size;
|
|
private ulong _elapsedCycles;
|
|
|
|
private void InitializeSettings()
|
|
{
|
|
// TODO: clean this stuff up
|
|
/*****************************************/
|
|
int size = Core.Rom.Length;
|
|
_size = Core.Rom.Length < 8448 ? 8448 : Core.Rom.Length; //8448 or Rom size, whichever is bigger
|
|
|
|
_numberOfLoadImages = (byte)(_size / 8448);
|
|
|
|
// TODO: why are we making a redundant copy?
|
|
_loadedImages = new ByteBuffer(_size);
|
|
for (int i = 0; i < size; i++)
|
|
{
|
|
_loadedImages[i] = Core.Rom[i];
|
|
}
|
|
|
|
if (size < 8448)
|
|
{
|
|
for (int i = size; i < _size; i++)
|
|
{
|
|
_loadedImages[i] = DefaultHeader[i];
|
|
}
|
|
}
|
|
/*****************************************/
|
|
|
|
InitializeRom();
|
|
BankConfiguration(0);
|
|
}
|
|
|
|
#region SuperCharger Data
|
|
|
|
private readonly byte[] DummyRomCode = {
|
|
0xa5, 0xfa, 0x85, 0x80, 0x4c, 0x18, 0xf8, 0xff,
|
|
0xff, 0xff, 0x78, 0xd8, 0xa0, 0x00, 0xa2, 0x00,
|
|
0x94, 0x00, 0xe8, 0xd0, 0xfb, 0x4c, 0x50, 0xf8,
|
|
0xa2, 0x00, 0xbd, 0x06, 0xf0, 0xad, 0xf8, 0xff,
|
|
0xa2, 0x00, 0xad, 0x00, 0xf0, 0xea, 0xbd, 0x00,
|
|
0xf7, 0xca, 0xd0, 0xf6, 0x4c, 0x50, 0xf8, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xa2, 0x03, 0xbc, 0x22, 0xf9, 0x94, 0xfa, 0xca,
|
|
0x10, 0xf8, 0xa0, 0x00, 0xa2, 0x28, 0x94, 0x04,
|
|
0xca, 0x10, 0xfb, 0xa2, 0x1c, 0x94, 0x81, 0xca,
|
|
0x10, 0xfb, 0xa9, 0xff, 0xc9, 0x00, 0xd0, 0x03,
|
|
0x4c, 0x13, 0xf9, 0xa9, 0x00, 0x85, 0x1b, 0x85,
|
|
0x1c, 0x85, 0x1d, 0x85, 0x1e, 0x85, 0x1f, 0x85,
|
|
0x19, 0x85, 0x1a, 0x85, 0x08, 0x85, 0x01, 0xa9,
|
|
0x10, 0x85, 0x21, 0x85, 0x02, 0xa2, 0x07, 0xca,
|
|
0xca, 0xd0, 0xfd, 0xa9, 0x00, 0x85, 0x20, 0x85,
|
|
0x10, 0x85, 0x11, 0x85, 0x02, 0x85, 0x2a, 0xa9,
|
|
0x05, 0x85, 0x0a, 0xa9, 0xff, 0x85, 0x0d, 0x85,
|
|
0x0e, 0x85, 0x0f, 0x85, 0x84, 0x85, 0x85, 0xa9,
|
|
0xf0, 0x85, 0x83, 0xa9, 0x74, 0x85, 0x09, 0xa9,
|
|
0x0c, 0x85, 0x15, 0xa9, 0x1f, 0x85, 0x17, 0x85,
|
|
0x82, 0xa9, 0x07, 0x85, 0x19, 0xa2, 0x08, 0xa0,
|
|
0x00, 0x85, 0x02, 0x88, 0xd0, 0xfb, 0x85, 0x02,
|
|
0x85, 0x02, 0xa9, 0x02, 0x85, 0x02, 0x85, 0x00,
|
|
0x85, 0x02, 0x85, 0x02, 0x85, 0x02, 0xa9, 0x00,
|
|
0x85, 0x00, 0xca, 0x10, 0xe4, 0x06, 0x83, 0x66,
|
|
0x84, 0x26, 0x85, 0xa5, 0x83, 0x85, 0x0d, 0xa5,
|
|
0x84, 0x85, 0x0e, 0xa5, 0x85, 0x85, 0x0f, 0xa6,
|
|
0x82, 0xca, 0x86, 0x82, 0x86, 0x17, 0xe0, 0x0a,
|
|
0xd0, 0xc3, 0xa9, 0x02, 0x85, 0x01, 0xa2, 0x1c,
|
|
0xa0, 0x00, 0x84, 0x19, 0x84, 0x09, 0x94, 0x81,
|
|
0xca, 0x10, 0xfb, 0xa6, 0x80, 0xdd, 0x00, 0xf0,
|
|
0xa9, 0x9a, 0xa2, 0xff, 0xa0, 0x00, 0x9a, 0x4c,
|
|
0xfa, 0x00, 0xcd, 0xf8, 0xff, 0x4c
|
|
};
|
|
|
|
private readonly byte[] DefaultHeader = {
|
|
0xac, 0xfa, 0x0f, 0x18, 0x62, 0x00, 0x24, 0x02,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0x00, 0x04, 0x08, 0x0c, 0x10, 0x14, 0x18, 0x1c,
|
|
0x01, 0x05, 0x09, 0x0d, 0x11, 0x15, 0x19, 0x1d,
|
|
0x02, 0x06, 0x0a, 0x0e, 0x12, 0x16, 0x1a, 0x1e,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00
|
|
};
|
|
|
|
#endregion
|
|
|
|
public override bool HasCartRam
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
public override ByteBuffer CartRam
|
|
{
|
|
get { return _superChargerImage; }
|
|
}
|
|
|
|
public override void HardReset()
|
|
{
|
|
_superChargerImage = new ByteBuffer(8192);
|
|
_imageOffsets = new IntBuffer(2);
|
|
_writePending = false;
|
|
_distinctAccesses = 0;
|
|
|
|
_writeEnabled = false;
|
|
_dataHoldRegister = 0;
|
|
_numberOfLoadImages = 0;
|
|
_loadedImages = null;
|
|
|
|
_header = new ByteBuffer(256);
|
|
_powerIndicator = false;
|
|
_powerRomCycle = 0;
|
|
_size = 0;
|
|
|
|
_elapsedCycles = 0;
|
|
|
|
InitializeSettings();
|
|
base.HardReset();
|
|
}
|
|
|
|
public override void Dispose()
|
|
{
|
|
_superChargerImage.Dispose();
|
|
_imageOffsets.Dispose();
|
|
_loadedImages.Dispose();
|
|
base.Dispose();
|
|
}
|
|
|
|
public override void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync("superChargerImage", ref _superChargerImage);
|
|
ser.Sync("imageOffsets", ref _imageOffsets);
|
|
ser.Sync("writePending", ref _writePending);
|
|
ser.Sync("distinctAccesses", ref _distinctAccesses);
|
|
|
|
ser.Sync("writeEnabled", ref _writeEnabled);
|
|
ser.Sync("dataHoldRegister", ref _dataHoldRegister);
|
|
ser.Sync("numberOfLoadImages", ref _numberOfLoadImages);
|
|
ser.Sync("loadedImages", ref _loadedImages);
|
|
|
|
ser.Sync("header", ref _header);
|
|
ser.Sync("powerIndicator", ref _powerIndicator);
|
|
ser.Sync("powerRomCycle", ref _powerRomCycle);
|
|
ser.Sync("size", ref _size);
|
|
|
|
ser.Sync("elapsedCycles", ref _elapsedCycles);
|
|
|
|
base.SyncState(ser);
|
|
}
|
|
|
|
public override void ClockCpu()
|
|
{
|
|
_elapsedCycles++;
|
|
|
|
}
|
|
|
|
private byte ReadMem(ushort addr, bool peek)
|
|
{
|
|
if (addr < 0x1000) { if (peek) { return base.PeekMemory(addr); } else { return base.ReadMemory(addr); } }
|
|
|
|
/*---------------------------*/
|
|
|
|
if (addr == 0x1850 && _imageOffsets[1] == (3 << 11))
|
|
{
|
|
LoadIntoRam(Core.MemoryDomains["System Bus"].PeekByte(0x80)); // Get load that's being accessed (BIOS places load number at 0x80) // TODO: a better way to do this
|
|
return _superChargerImage[(addr & 0x7FF) + _imageOffsets[1]];
|
|
}
|
|
|
|
if (_writePending && // Cancel any pending write if more than 5 distinct accesses have occurred // TODO: Modify to handle when the distinct counter wraps around...
|
|
(Core.DistinctAccessCount > _distinctAccesses + 5))
|
|
{
|
|
_writePending = false;
|
|
}
|
|
|
|
/*---------------------------*/
|
|
|
|
if (!((addr & 0x0F00) > 0) && (!_writeEnabled || !_writePending))
|
|
{
|
|
_dataHoldRegister = (byte)addr;
|
|
_distinctAccesses = Core.DistinctAccessCount;
|
|
_writePending = true;
|
|
}
|
|
else if ((addr & 0x1FFF) == 0x1FF8) // Is the bank configuration hotspot being accessed?
|
|
{
|
|
_writePending = false;
|
|
BankConfiguration(_dataHoldRegister);
|
|
|
|
}
|
|
else if (_writeEnabled && _writePending &&
|
|
Core.DistinctAccessCount == (_distinctAccesses + 5))
|
|
{
|
|
if ((addr & 0x800) == 0)
|
|
{
|
|
_superChargerImage[(addr & 0x07FF) + _imageOffsets[0]] = _dataHoldRegister;
|
|
}
|
|
else if (_imageOffsets[1] != (3 << 11)) // Don't poke Rom
|
|
{
|
|
_superChargerImage[(addr & 0x07FF) + _imageOffsets[1]] = _dataHoldRegister;
|
|
}
|
|
|
|
_writePending = false;
|
|
}
|
|
|
|
/*---------------------------*/
|
|
|
|
return _superChargerImage[(addr & 0x07FF) + _imageOffsets[((addr & 0x800) > 0) ? 1 : 0]];
|
|
}
|
|
|
|
public override byte ReadMemory(ushort addr)
|
|
{
|
|
return ReadMem(addr, false);
|
|
}
|
|
|
|
public override byte PeekMemory(ushort addr)
|
|
{
|
|
return ReadMem(addr, true);
|
|
}
|
|
|
|
private void WriteMem(ushort addr, byte value, bool poke)
|
|
{
|
|
if (addr < 0x1000)
|
|
{
|
|
base.WriteMemory(addr, value);
|
|
return;
|
|
}
|
|
|
|
if (!poke && _writePending && (Core.DistinctAccessCount > _distinctAccesses + 5))
|
|
{
|
|
_writePending = false;
|
|
}
|
|
|
|
// Is the data hold register being set?
|
|
if (!poke && !((addr & 0x0F00) > 0) && (!_writeEnabled || !_writePending))
|
|
{
|
|
_dataHoldRegister = (byte)addr;
|
|
_distinctAccesses = Core.DistinctAccessCount;
|
|
_writePending = true;
|
|
}
|
|
// Is the bank configuration hotspot being accessed?
|
|
else if (!poke && (addr & 0x1FFF) == 0x1FF8)
|
|
{
|
|
// Yes, so handle bank configuration
|
|
_writePending = false;
|
|
BankConfiguration(_dataHoldRegister);
|
|
}
|
|
|
|
// Handle poke if writing enabled
|
|
else if (_writeEnabled && _writePending &&
|
|
(Core.DistinctAccessCount == (_distinctAccesses + 5)))
|
|
{
|
|
if ((addr & 0x0800) == 0)
|
|
{
|
|
_superChargerImage[(addr & 0x07FF) + _imageOffsets[0]] = _dataHoldRegister;
|
|
}
|
|
else if (_imageOffsets[1] != (3 << 11)) // Can't poke to ROM
|
|
{
|
|
_superChargerImage[(addr & 0x07FF) + _imageOffsets[1]] = _dataHoldRegister;
|
|
}
|
|
|
|
_writePending = false;
|
|
}
|
|
}
|
|
|
|
public override void WriteMemory(ushort addr, byte value)
|
|
{
|
|
WriteMem(addr, value, poke: false);
|
|
}
|
|
|
|
public override void PokeMemory(ushort addr, byte value)
|
|
{
|
|
WriteMem(addr, value, poke: true);
|
|
}
|
|
|
|
private void InitializeRom()
|
|
{
|
|
/* scrom.asm data borrowed from Stella:
|
|
// Note that the following offsets depend on the 'scrom.asm' file
|
|
// in src/emucore/misc. If that file is ever recompiled (and its
|
|
// contents placed in the ourDummyROMCode array), the offsets will
|
|
// almost definitely change
|
|
*/
|
|
|
|
// The scrom.asm code checks a value at offset 109 as follows:
|
|
// 0xFF -> do a complete jump over the SC BIOS progress bars code
|
|
// 0x00 -> show SC BIOS progress bars as normal
|
|
DummyRomCode[109] = (byte)(Core.SyncSettings.FastScBios ? 0xFF : 0x00);
|
|
|
|
// Stella does this, but randomness is bad for determinacy! Hopefully we don't really need it
|
|
//ourDummyROMCode[281] = mySystem->randGenerator().next();
|
|
|
|
// Initialize ROM with illegal 6502 opcode that causes a real 6502 to jam
|
|
for (int i = 0; i < 2048; i++)
|
|
{
|
|
_superChargerImage[(3 << 11) + i] = 0x02;
|
|
}
|
|
|
|
// Copy the "dummy" Supercharger BIOS code into the ROM area
|
|
for (int i = 0; i < DummyRomCode.Length; i++)
|
|
{
|
|
_superChargerImage[(3 << 11) + i] = DummyRomCode[i];
|
|
}
|
|
|
|
// Finally set 6502 vectors to point to initial load code at 0xF80A of BIOS
|
|
_superChargerImage[(3 << 11) + 2044] = 0x0A;
|
|
_superChargerImage[(3 << 11) + 2045] = 0xF8;
|
|
_superChargerImage[(3 << 11) + 2046] = 0x0A;
|
|
_superChargerImage[(3 << 11) + 2047] = 0xF8;
|
|
}
|
|
|
|
private void BankConfiguration(byte configuration)
|
|
{
|
|
// D7-D5 of this byte: Write Pulse Delay (n/a for emulator)
|
|
//
|
|
// D4-D0: RAM/ROM configuration:
|
|
// $F000-F7FF $F800-FFFF Address range that banks map into
|
|
// 000wp 2 ROM
|
|
// 001wp 0 ROM
|
|
// 010wp 2 0 as used in Commie Mutants and many others
|
|
// 011wp 0 2 as used in Suicide Mission
|
|
// 100wp 2 ROM
|
|
// 101wp 1 ROM
|
|
// 110wp 2 1 as used in Killer Satellites
|
|
// 111wp 1 2 as we use for 2k/4k ROM cloning
|
|
//
|
|
// w = Write Enable (1 = enabled; accesses to $F000-$F0FF cause writes
|
|
// to happen. 0 = disabled, and the cart acts like ROM.)
|
|
// p = ROM Power (0 = enabled, 1 = off.) Only power the ROM if you're
|
|
// wanting to access the ROM for multiloads. Otherwise set to 1.
|
|
|
|
//_bank2k = configuration & 0x1F; // remember for the bank() method
|
|
_powerIndicator = !((configuration & 0x01) > 0);
|
|
if (_powerIndicator)
|
|
{
|
|
_powerRomCycle = (int)_elapsedCycles;
|
|
}
|
|
|
|
_writeEnabled = (configuration & 0x02) > 0;
|
|
|
|
switch ((configuration >> 2) & 0x07)
|
|
{
|
|
case 0x00:
|
|
_imageOffsets[0] = 2 << 11;
|
|
_imageOffsets[1] = 3 << 11;
|
|
break;
|
|
case 0x01:
|
|
_imageOffsets[0] = 0;
|
|
_imageOffsets[1] = 3 << 11;
|
|
break;
|
|
case 0x02:
|
|
_imageOffsets[0] = 2 << 11;
|
|
_imageOffsets[1] = 0;
|
|
break;
|
|
case 0x03:
|
|
_imageOffsets[0] = 0;
|
|
_imageOffsets[1] = 2 << 11;
|
|
break;
|
|
case 0x04:
|
|
_imageOffsets[0] = 2 << 11;
|
|
_imageOffsets[1] = 3 << 11;
|
|
break;
|
|
case 0x05:
|
|
_imageOffsets[0] = 1 << 11;
|
|
_imageOffsets[1] = 3 << 11;
|
|
break;
|
|
case 0x06:
|
|
_imageOffsets[0] = 2 << 11;
|
|
_imageOffsets[1] = 1 << 11;
|
|
break;
|
|
case 0x07:
|
|
_imageOffsets[0] = 1 << 11;
|
|
_imageOffsets[1] = 2 << 11;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void LoadIntoRam(byte load)
|
|
{
|
|
ushort image;
|
|
|
|
for (image = 0; image < _numberOfLoadImages; image++)
|
|
{
|
|
if (_loadedImages[(image * 8448) + 8192 + 5] == load)
|
|
{
|
|
for (int i = 0; i < 256; i++)
|
|
{
|
|
_header[i] = _loadedImages[(image * 8448) + 8192 + i];
|
|
}
|
|
|
|
if (Checksum(_header.Arr.Take(8).ToArray()) != 0x55)
|
|
{
|
|
Console.WriteLine("WARNING: The Supercharger header checksum is invalid...");
|
|
}
|
|
|
|
// TODO: verify the load's header
|
|
|
|
// Load all of the pages from the load
|
|
bool invalidPageChecksumSeen = false;
|
|
for (int j = 0; j < _header[3]; j++)
|
|
{
|
|
int bank = _header[16 + j] & 0x03;
|
|
int page = (_header[16 + j] >> 2) & 0x07;
|
|
var src = _loadedImages.Arr.Skip((image * 8448) + (j * 256)).Take(256).ToArray();
|
|
byte sum = (byte)(Checksum(src) + _header[16 + j] + _header[64 + j]);
|
|
|
|
if (!invalidPageChecksumSeen && (sum != 0x55))
|
|
{
|
|
Console.WriteLine("WARNING: Some Supercharger page checksums are invalid...");
|
|
invalidPageChecksumSeen = true;
|
|
}
|
|
|
|
if (bank < 3)
|
|
{
|
|
for (int k = 0; k < src.Length; k++)
|
|
{
|
|
_superChargerImage[(bank * 2048) + (page * 256) + k] = src[k];
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: is this the correct Write to do?
|
|
base.WriteMemory(0xFE, _header[0]);
|
|
base.WriteMemory(0xFF, _header[1]);
|
|
base.WriteMemory(0x80, _header[2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
private byte Checksum(byte[] s)
|
|
{
|
|
byte sum = 0;
|
|
|
|
for (int i = 0; i < s.Count(); i++)
|
|
{
|
|
sum += s[i];
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
}
|
|
}
|