2019-02-13 18:44:39 -05:00
|
|
|
#include "stdafx.h"
|
|
|
|
#include "Ppu.h"
|
|
|
|
#include "Console.h"
|
2019-02-13 23:03:01 -05:00
|
|
|
#include "MemoryManager.h"
|
2019-02-13 18:44:39 -05:00
|
|
|
#include "Cpu.h"
|
2019-02-16 11:23:01 -05:00
|
|
|
#include "Spc.h"
|
2019-02-17 15:37:31 -05:00
|
|
|
#include "InternalRegisters.h"
|
2019-02-17 19:54:29 -05:00
|
|
|
#include "ControlManager.h"
|
2019-02-13 23:03:01 -05:00
|
|
|
#include "VideoDecoder.h"
|
2019-02-15 21:33:13 -05:00
|
|
|
#include "NotificationManager.h"
|
2019-02-13 18:44:39 -05:00
|
|
|
|
|
|
|
Ppu::Ppu(shared_ptr<Console> console)
|
|
|
|
{
|
|
|
|
_console = console;
|
2019-02-17 15:37:31 -05:00
|
|
|
_regs = console->GetInternalRegisters();
|
2019-02-13 18:44:39 -05:00
|
|
|
|
2019-02-16 01:16:57 -05:00
|
|
|
_outputBuffers[0] = new uint16_t[256 * 224];
|
|
|
|
_outputBuffers[1] = new uint16_t[256 * 224];
|
|
|
|
|
|
|
|
_currentBuffer = _outputBuffers[0];
|
2019-02-13 18:44:39 -05:00
|
|
|
|
|
|
|
_layerConfig[0] = {};
|
|
|
|
_layerConfig[1] = {};
|
|
|
|
_layerConfig[2] = {};
|
|
|
|
_layerConfig[3] = {};
|
|
|
|
|
|
|
|
_cgramAddress = 0;
|
|
|
|
|
2019-02-16 01:16:57 -05:00
|
|
|
_vram = new uint8_t[Ppu::VideoRamSize];
|
|
|
|
memset(_vram, 0, Ppu::VideoRamSize);
|
|
|
|
|
2019-02-13 18:44:39 -05:00
|
|
|
_vramAddress = 0;
|
|
|
|
_vramIncrementValue = 1;
|
|
|
|
_vramAddressRemapping = 0;
|
|
|
|
_vramAddrIncrementOnSecondReg = false;
|
|
|
|
}
|
|
|
|
|
2019-02-16 01:16:57 -05:00
|
|
|
Ppu::~Ppu()
|
|
|
|
{
|
|
|
|
delete[] _vram;
|
|
|
|
delete[] _outputBuffers[0];
|
|
|
|
delete[] _outputBuffers[1];
|
|
|
|
}
|
|
|
|
|
2019-02-17 19:54:29 -05:00
|
|
|
uint32_t Ppu::GetFrameCount()
|
|
|
|
{
|
|
|
|
return _frameCount;
|
|
|
|
}
|
|
|
|
|
2019-02-13 18:44:39 -05:00
|
|
|
PpuState Ppu::GetState()
|
|
|
|
{
|
|
|
|
return {
|
|
|
|
_cycle,
|
|
|
|
_scanline,
|
|
|
|
_frameCount
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ppu::Exec()
|
|
|
|
{
|
|
|
|
if(_cycle == 340) {
|
2019-02-16 11:23:01 -05:00
|
|
|
_cycle = -1;
|
2019-02-13 18:44:39 -05:00
|
|
|
_scanline++;
|
|
|
|
|
|
|
|
if(_scanline == 225) {
|
2019-02-17 21:09:33 -05:00
|
|
|
//Reset OAM address at the start of vblank?
|
|
|
|
_internalOamAddress = (_oamRamAddress << 1);
|
|
|
|
|
2019-02-17 19:54:29 -05:00
|
|
|
_frameCount++;
|
2019-02-16 11:23:01 -05:00
|
|
|
_console->GetSpc()->ProcessEndFrame();
|
2019-02-17 19:54:29 -05:00
|
|
|
_console->GetControlManager()->UpdateInputState();
|
|
|
|
_regs->ProcessAutoJoypadRead();
|
|
|
|
_regs->SetNmiFlag(true);
|
2019-02-13 18:44:39 -05:00
|
|
|
SendFrame();
|
|
|
|
|
2019-02-17 15:37:31 -05:00
|
|
|
if(_regs->IsNmiEnabled()) {
|
2019-02-13 18:44:39 -05:00
|
|
|
_console->GetCpu()->SetNmiFlag();
|
|
|
|
}
|
2019-02-17 01:09:47 -05:00
|
|
|
} else if(_scanline == 261) {
|
2019-02-17 19:54:29 -05:00
|
|
|
_regs->SetNmiFlag(false);
|
2019-02-13 18:44:39 -05:00
|
|
|
_scanline = 0;
|
|
|
|
}
|
2019-02-17 01:09:47 -05:00
|
|
|
|
2019-02-17 15:37:31 -05:00
|
|
|
if(_regs->IsVerticalIrqEnabled() && !_regs->IsHorizontalIrqEnabled() && _scanline == _regs->GetVerticalTimer()) {
|
2019-02-17 01:09:47 -05:00
|
|
|
//An IRQ will occur sometime just after the V Counter reaches the value set in $4209/$420A.
|
|
|
|
_console->GetCpu()->SetIrqSource(IrqSource::Ppu);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-17 15:37:31 -05:00
|
|
|
if(_regs->IsHorizontalIrqEnabled() && _cycle == _regs->GetHorizontalTimer() && (!_regs->IsVerticalIrqEnabled() || _scanline == _regs->GetVerticalTimer())) {
|
2019-02-17 01:09:47 -05:00
|
|
|
//An IRQ will occur sometime just after the H Counter reaches the value set in $4207/$4208.
|
|
|
|
_console->GetCpu()->SetIrqSource(IrqSource::Ppu);
|
2019-02-13 18:44:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
_cycle++;
|
|
|
|
}
|
|
|
|
|
2019-02-17 23:26:49 -05:00
|
|
|
void Ppu::RenderTilemap(uint8_t layerIndex, uint8_t bpp)
|
2019-02-13 18:44:39 -05:00
|
|
|
{
|
2019-02-17 23:26:49 -05:00
|
|
|
if(((_mainScreenLayers >> layerIndex) & 0x01) == 0) {
|
|
|
|
//This screen is disabled
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
LayerConfig &config = _layerConfig[layerIndex];
|
|
|
|
|
2019-02-17 00:32:41 -05:00
|
|
|
uint16_t tilemapAddr = config.TilemapAddress;
|
|
|
|
uint16_t chrAddr = config.ChrAddress;
|
2019-02-13 23:03:01 -05:00
|
|
|
|
2019-02-17 00:32:41 -05:00
|
|
|
uint32_t addr = tilemapAddr;
|
2019-02-13 23:03:01 -05:00
|
|
|
for(int y = 0; y < 28; y++) {
|
|
|
|
for(int x = 0; x < 32; x++) {
|
2019-02-17 00:32:41 -05:00
|
|
|
uint8_t palette = (_vram[addr + 1] >> 2) & 0x07;
|
|
|
|
uint16_t tileIndex = ((_vram[addr + 1] & 0x03) << 8) | _vram[addr];
|
2019-02-13 23:03:01 -05:00
|
|
|
|
2019-02-17 00:32:41 -05:00
|
|
|
uint16_t tileStart = chrAddr + tileIndex * 8 * bpp;
|
2019-02-13 23:03:01 -05:00
|
|
|
for(int i = 0; i < 8; i++) {
|
|
|
|
for(int j = 0; j < 8; j++) {
|
2019-02-17 00:32:41 -05:00
|
|
|
uint16_t color = 0;
|
|
|
|
for(int plane = 0; plane < bpp; plane++) {
|
|
|
|
uint8_t offset = (plane >> 1) * 16;
|
|
|
|
color |= (((_vram[tileStart + i * 2 + offset + (plane & 0x01)] >> (7 - j)) & 0x01) << bpp);
|
2019-02-13 23:03:01 -05:00
|
|
|
color >>= 1;
|
|
|
|
}
|
|
|
|
|
2019-02-17 23:26:49 -05:00
|
|
|
if(color > 0) {
|
|
|
|
uint16_t paletteRamOffset = (palette * (1 << bpp) + color) * 2;
|
|
|
|
uint16_t paletteColor = _cgram[paletteRamOffset] | (_cgram[paletteRamOffset + 1] << 8);
|
|
|
|
_currentBuffer[(y * 8 + i) * 256 + x * 8 + j] = paletteColor;
|
|
|
|
}
|
2019-02-13 23:03:01 -05:00
|
|
|
}
|
|
|
|
}
|
2019-02-17 00:32:41 -05:00
|
|
|
addr+=2;
|
2019-02-13 23:03:01 -05:00
|
|
|
}
|
2019-02-13 18:44:39 -05:00
|
|
|
}
|
2019-02-17 00:32:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void Ppu::SendFrame()
|
|
|
|
{
|
2019-02-17 23:26:49 -05:00
|
|
|
uint16_t bgColor = _cgram[0] | (_cgram[1]);
|
|
|
|
for(int i = 0; i < 256 * 224; i++) {
|
|
|
|
_currentBuffer[i] = bgColor;
|
|
|
|
}
|
|
|
|
|
2019-02-17 00:32:41 -05:00
|
|
|
switch(_bgMode) {
|
|
|
|
case 0:
|
2019-02-17 23:26:49 -05:00
|
|
|
RenderTilemap(3, 2);
|
|
|
|
RenderTilemap(2, 2);
|
|
|
|
RenderTilemap(1, 2);
|
|
|
|
RenderTilemap(0, 2);
|
2019-02-17 00:32:41 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
2019-02-17 23:26:49 -05:00
|
|
|
RenderTilemap(2, 2);
|
|
|
|
RenderTilemap(1, 4);
|
|
|
|
RenderTilemap(0, 4);
|
2019-02-17 00:32:41 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 2:
|
2019-02-17 23:26:49 -05:00
|
|
|
RenderTilemap(1, 4);
|
|
|
|
RenderTilemap(0, 4);
|
2019-02-17 00:32:41 -05:00
|
|
|
break;
|
2019-02-13 18:44:39 -05:00
|
|
|
|
2019-02-17 00:32:41 -05:00
|
|
|
case 3:
|
2019-02-17 23:26:49 -05:00
|
|
|
RenderTilemap(1, 4);
|
|
|
|
RenderTilemap(0, 8);
|
2019-02-17 00:32:41 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 5:
|
2019-02-17 23:26:49 -05:00
|
|
|
RenderTilemap(1, 2);
|
|
|
|
RenderTilemap(0, 4);
|
2019-02-17 00:32:41 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 6:
|
2019-02-17 23:26:49 -05:00
|
|
|
RenderTilemap(0, 8);
|
2019-02-17 00:32:41 -05:00
|
|
|
break;
|
|
|
|
}
|
2019-02-17 22:44:57 -05:00
|
|
|
|
|
|
|
//Draw sprites
|
2019-02-17 23:26:49 -05:00
|
|
|
if(_mainScreenLayers & 0x10) {
|
|
|
|
DrawSprites();
|
|
|
|
}
|
|
|
|
|
|
|
|
_console->GetNotificationManager()->SendNotification(ConsoleNotificationType::PpuFrameDone);
|
|
|
|
_currentBuffer = _currentBuffer == _outputBuffers[0] ? _outputBuffers[1] : _outputBuffers[0];
|
|
|
|
_console->GetVideoDecoder()->UpdateFrame(_currentBuffer, _frameCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ppu::DrawSprites()
|
|
|
|
{
|
|
|
|
for(int i = 0; i < 512; i += 4) {
|
2019-02-17 22:44:57 -05:00
|
|
|
uint8_t y = _oamRam[i + 1];
|
|
|
|
if(y >= 225) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
int16_t x = _oamRam[i];
|
|
|
|
uint8_t tileRow = (_oamRam[i + 2] & 0xF0) >> 4;
|
|
|
|
uint8_t tileColumn = _oamRam[i + 2] & 0x0F;
|
|
|
|
uint8_t flags = _oamRam[i + 3];
|
|
|
|
|
|
|
|
uint8_t palette = (flags >> 1) & 0x07;
|
|
|
|
bool useSecondTable = (flags & 0x01) != 0;
|
|
|
|
|
|
|
|
//TODO: vertical, horizontal flip, priority
|
|
|
|
|
|
|
|
uint8_t highTableOffset = i >> 4;
|
|
|
|
uint8_t shift = ((i >> 2) & 0x03) << 1;
|
|
|
|
|
|
|
|
uint8_t highTableValue = _oamRam[0x200 | highTableOffset] >> shift;
|
|
|
|
bool negativeX = (highTableValue & 0x01) != 0;
|
|
|
|
uint8_t size = (highTableValue & 0x02) >> 1;
|
|
|
|
|
|
|
|
if(negativeX) {
|
|
|
|
x = -x;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(int rowOffset = 0; rowOffset < _oamSizes[_oamMode][size][1]; rowOffset++) {
|
|
|
|
for(int columnOffset = 0; columnOffset < _oamSizes[_oamMode][size][0]; columnOffset++) {
|
|
|
|
uint8_t column = (tileColumn + columnOffset) & 0x0F;
|
|
|
|
uint8_t row = (tileRow + rowOffset) & 0x0F;
|
|
|
|
uint8_t tileIndex = (row << 4) | column;
|
|
|
|
|
|
|
|
uint16_t tileStart = ((_oamBaseAddress + (tileIndex << 4) + (useSecondTable ? _oamAddressOffset : 0)) & 0x7FFF) << 1;
|
|
|
|
uint16_t bpp = 4;
|
|
|
|
for(int i = 0; i < 8; i++) {
|
|
|
|
for(int j = 0; j < 8; j++) {
|
|
|
|
uint16_t color = 0;
|
|
|
|
for(int plane = 0; plane < bpp; plane++) {
|
|
|
|
uint8_t offset = (plane >> 1) * 16;
|
|
|
|
color |= (((_vram[tileStart + i * 2 + offset + (plane & 0x01)] >> (7 - j)) & 0x01) << bpp);
|
|
|
|
color >>= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(color != 0) {
|
|
|
|
uint16_t paletteRamOffset = 256 + ((palette * (1 << bpp) + color) * 2);
|
|
|
|
|
|
|
|
uint16_t paletteColor = _cgram[paletteRamOffset] | (_cgram[paletteRamOffset + 1] << 8);
|
|
|
|
_currentBuffer[(y + i + rowOffset * 8) * 256 + x + j + columnOffset * 8] = paletteColor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-02-13 18:44:39 -05:00
|
|
|
}
|
|
|
|
|
2019-02-15 21:33:13 -05:00
|
|
|
uint8_t* Ppu::GetVideoRam()
|
|
|
|
{
|
|
|
|
return _vram;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t* Ppu::GetCgRam()
|
|
|
|
{
|
|
|
|
return _cgram;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t* Ppu::GetSpriteRam()
|
|
|
|
{
|
2019-02-17 21:09:33 -05:00
|
|
|
return _oamRam;
|
2019-02-15 21:33:13 -05:00
|
|
|
}
|
|
|
|
|
2019-02-13 18:44:39 -05:00
|
|
|
uint8_t Ppu::Read(uint16_t addr)
|
|
|
|
{
|
|
|
|
switch(addr) {
|
2019-02-17 14:42:35 -05:00
|
|
|
default:
|
|
|
|
MessageManager::DisplayMessage("Debug", "Unimplemented register read: " + HexUtilities::ToHex(addr));
|
|
|
|
break;
|
2019-02-13 18:44:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Ppu::Write(uint32_t addr, uint8_t value)
|
|
|
|
{
|
|
|
|
switch(addr) {
|
2019-02-17 19:54:29 -05:00
|
|
|
case 0x2101:
|
2019-02-17 21:09:33 -05:00
|
|
|
_oamMode = (value & 0xE0) >> 5;
|
|
|
|
_oamBaseAddress = (value & 0x07) << 13;
|
2019-02-17 22:44:57 -05:00
|
|
|
_oamAddressOffset = (((value & 0x18) >> 3) + 1) << 12;
|
2019-02-17 19:54:29 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x2102:
|
2019-02-17 21:09:33 -05:00
|
|
|
_oamRamAddress = (_oamRamAddress & 0x100) | value;
|
|
|
|
_internalOamAddress = (_oamRamAddress << 1);
|
2019-02-17 19:54:29 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x2103:
|
2019-02-17 21:09:33 -05:00
|
|
|
_oamRamAddress = (_oamRamAddress & 0xFF) | ((value & 0x01) << 8);
|
|
|
|
_internalOamAddress = (_oamRamAddress << 1);
|
|
|
|
_enableOamPriority = (value & 0x80) != 0;
|
2019-02-17 19:54:29 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x2104:
|
2019-02-17 21:09:33 -05:00
|
|
|
if(_internalOamAddress < 512) {
|
|
|
|
if(_internalOamAddress & 0x01) {
|
|
|
|
_oamRam[_internalOamAddress - 1] = _oamWriteBuffer;
|
|
|
|
_oamRam[_internalOamAddress] = value;
|
|
|
|
} else {
|
|
|
|
_oamWriteBuffer = value;
|
|
|
|
}
|
|
|
|
} else if(_internalOamAddress >= 512) {
|
|
|
|
uint16_t address = 0x200 | (_internalOamAddress & 0x1F);
|
|
|
|
_oamRam[address] = value;
|
|
|
|
}
|
|
|
|
_internalOamAddress = (_internalOamAddress + 1) & 0x3FF;
|
2019-02-17 19:54:29 -05:00
|
|
|
break;
|
|
|
|
|
2019-02-17 00:32:41 -05:00
|
|
|
case 0x2105:
|
|
|
|
_bgMode = value & 0x07;
|
2019-02-17 01:09:47 -05:00
|
|
|
|
|
|
|
//TODO
|
2019-02-17 00:32:41 -05:00
|
|
|
//_mode1Bg3Priority = (value & 0x08) != 0;
|
2019-02-17 01:09:47 -05:00
|
|
|
|
2019-02-17 00:32:41 -05:00
|
|
|
_layerConfig[0].LargeTiles = (value & 0x10) != 0;
|
|
|
|
_layerConfig[1].LargeTiles = (value & 0x20) != 0;
|
|
|
|
_layerConfig[2].LargeTiles = (value & 0x30) != 0;
|
|
|
|
_layerConfig[3].LargeTiles = (value & 0x40) != 0;
|
|
|
|
break;
|
|
|
|
|
2019-02-13 18:44:39 -05:00
|
|
|
case 0x2107: case 0x2108: case 0x2109: case 0x210A:
|
|
|
|
//BG 1-4 Tilemap Address and Size (BG1SC, BG2SC, BG3SC, BG4SC)
|
2019-02-13 23:03:01 -05:00
|
|
|
_layerConfig[addr - 0x2107].TilemapAddress = (value & 0xFC) << 9;
|
2019-02-13 18:44:39 -05:00
|
|
|
_layerConfig[addr - 0x2107].HorizontalMirrorring = (value & 0x01) != 0;
|
|
|
|
_layerConfig[addr - 0x2107].VerticalMirrorring = (value & 0x02) != 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x210B: case 0x210C:
|
|
|
|
//BG1+2 / BG3+4 Chr Address (BG12NBA / BG34NBA)
|
2019-02-17 23:26:49 -05:00
|
|
|
_layerConfig[(addr - 0x210B) * 2].ChrAddress = (value & 0x0F) << 12;
|
|
|
|
_layerConfig[(addr - 0x210B) * 2 + 1].ChrAddress = (value & 0xF0) << 8;
|
2019-02-13 18:44:39 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x2115:
|
|
|
|
//VMAIN - Video Port Control
|
|
|
|
switch(value & 0x03) {
|
|
|
|
case 0: _vramIncrementValue = 1; break;
|
|
|
|
case 1: _vramIncrementValue = 32; break;
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
case 3: _vramIncrementValue = 128; break;
|
|
|
|
}
|
|
|
|
_vramAddressRemapping = (value & 0x0C) >> 2;
|
|
|
|
_vramAddrIncrementOnSecondReg = (value & 0x80) != 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x2116:
|
|
|
|
//VMADDL - VRAM Address low byte
|
2019-02-16 01:16:57 -05:00
|
|
|
_vramAddress = (_vramAddress & 0x7F00) | value;
|
2019-02-13 18:44:39 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x2117:
|
|
|
|
//VMADDH - VRAM Address high byte
|
2019-02-16 01:16:57 -05:00
|
|
|
_vramAddress = (_vramAddress & 0x00FF) | ((value & 0x7F) << 8);
|
2019-02-13 18:44:39 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x2118:
|
|
|
|
//VMDATAL - VRAM Data Write low byte
|
|
|
|
_vram[_vramAddress << 1] = value;
|
|
|
|
if(!_vramAddrIncrementOnSecondReg) {
|
2019-02-16 10:24:43 -05:00
|
|
|
_vramAddress = (_vramAddress + _vramIncrementValue) & 0x7FFF;
|
2019-02-13 18:44:39 -05:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x2119:
|
|
|
|
//VMDATAH - VRAM Data Write high byte
|
|
|
|
_vram[(_vramAddress << 1) + 1] = value;
|
|
|
|
if(_vramAddrIncrementOnSecondReg) {
|
2019-02-16 10:24:43 -05:00
|
|
|
_vramAddress = (_vramAddress + _vramIncrementValue) & 0x7FFF;
|
2019-02-13 18:44:39 -05:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x2121:
|
|
|
|
//CGRAM Address(CGADD)
|
2019-02-13 23:03:01 -05:00
|
|
|
_cgramAddress = value * 2;
|
2019-02-13 18:44:39 -05:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x2122:
|
|
|
|
//CGRAM Data write (CGDATA)
|
|
|
|
_cgram[_cgramAddress] = value;
|
2019-02-16 08:08:16 -05:00
|
|
|
_cgramAddress = (_cgramAddress + 1) & (Ppu::CgRamSize - 1);
|
2019-02-13 18:44:39 -05:00
|
|
|
break;
|
|
|
|
|
2019-02-17 23:26:49 -05:00
|
|
|
case 0x212C:
|
|
|
|
//TM - Main Screen Designation
|
|
|
|
_mainScreenLayers = value & 0x1F;
|
|
|
|
break;
|
|
|
|
|
2019-02-17 14:42:35 -05:00
|
|
|
default:
|
|
|
|
MessageManager::DisplayMessage("Debug", "Unimplemented register write: " + HexUtilities::ToHex(addr));
|
|
|
|
break;
|
2019-02-13 18:44:39 -05:00
|
|
|
}
|
|
|
|
}
|