mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-11 08:30:55 +00:00
Implemented seeking on tapes, mucked about a bit more with the Commodore analyser, at least temporarily removed cropping from the Vic emulator.
This commit is contained in:
parent
1ca4a2a012
commit
eeec516fa6
@ -65,9 +65,6 @@ template <class T> class MOS6560 {
|
||||
|
||||
// default to NTSC
|
||||
set_output_mode(OutputMode::NTSC);
|
||||
|
||||
// show only the centre
|
||||
_crt->set_visible_area(_crt->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f));
|
||||
}
|
||||
|
||||
void set_clock_rate(double clock_rate)
|
||||
@ -125,6 +122,18 @@ template <class T> class MOS6560 {
|
||||
}
|
||||
|
||||
_crt->set_new_display_type((unsigned int)(_timing.cycles_per_line*4), display_type);
|
||||
// _crt->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
|
||||
|
||||
// switch(output_mode)
|
||||
// {
|
||||
// case OutputMode::PAL:
|
||||
// _crt->set_visible_area(_crt->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
|
||||
// break;
|
||||
// case OutputMode::NTSC:
|
||||
// _crt->set_visible_area(_crt->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f));
|
||||
// break;
|
||||
// }
|
||||
|
||||
for(int c = 0; c < 16; c++)
|
||||
{
|
||||
_colours[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]);
|
||||
|
@ -269,6 +269,10 @@ void Machine::set_prg(const char *file_name, size_t length, const uint8_t *data)
|
||||
|
||||
#pragma mar - Tape
|
||||
|
||||
// LAB_FBDB = new tape byte setup;
|
||||
// loops at LAB_F92F
|
||||
// LAB_F8C0 = initiate tape read
|
||||
|
||||
void Machine::configure_as_target(const StaticAnalyser::Target &target)
|
||||
{
|
||||
if(target.tapes.size())
|
||||
|
@ -347,6 +347,7 @@
|
||||
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; };
|
||||
4BD14B111D74627C0088EAD6 /* AcornAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD14B0F1D74627C0088EAD6 /* AcornAnalyser.cpp */; };
|
||||
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
|
||||
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE77A2C1D84ADFB00BC3827 /* File.cpp */; };
|
||||
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; };
|
||||
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; };
|
||||
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
|
||||
@ -786,6 +787,8 @@
|
||||
4BD328FD1D7E3EB5003B8C44 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = ../../StaticAnalyser/TapeParser.hpp; sourceTree = "<group>"; };
|
||||
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
|
||||
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = "<group>"; };
|
||||
4BE77A2C1D84ADFB00BC3827 /* File.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = File.cpp; path = ../../StaticAnalyser/Commodore/File.cpp; sourceTree = "<group>"; };
|
||||
4BE77A2D1D84ADFB00BC3827 /* File.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = File.hpp; path = ../../StaticAnalyser/Commodore/File.hpp; sourceTree = "<group>"; };
|
||||
4BEE0A6A1D72496600532C7B /* Cartridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cartridge.cpp; sourceTree = "<group>"; };
|
||||
4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
|
||||
4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = "<group>"; };
|
||||
@ -1529,6 +1532,8 @@
|
||||
4BC830D01D6E7C690000A26F /* Tape.hpp */,
|
||||
4BC5E4931D7EE0E0008CF980 /* Utilities.cpp */,
|
||||
4BC5E4941D7EE0E0008CF980 /* Utilities.hpp */,
|
||||
4BE77A2C1D84ADFB00BC3827 /* File.cpp */,
|
||||
4BE77A2D1D84ADFB00BC3827 /* File.hpp */,
|
||||
);
|
||||
name = Commodore;
|
||||
sourceTree = "<group>";
|
||||
@ -2055,6 +2060,7 @@
|
||||
4BC830D11D6E7C690000A26F /* Tape.cpp in Sources */,
|
||||
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
|
||||
4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */,
|
||||
4BE77A2E1D84ADFB00BC3827 /* File.cpp in Sources */,
|
||||
4BAB62B51D327F7E00DF5BA0 /* G64.cpp in Sources */,
|
||||
4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */,
|
||||
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
|
||||
|
@ -36,54 +36,25 @@ void StaticAnalyser::Commodore::AddTargets(
|
||||
// continue if there are any files
|
||||
if(files.size())
|
||||
{
|
||||
bool is_basic = true;
|
||||
|
||||
// decide whether this is a BASIC file based on the proposition that:
|
||||
// (1) they're always relocatable; and
|
||||
// (2) they have a per-line structure of:
|
||||
// [4 bytes: address of start of next line]
|
||||
// [4 bytes: this line number]
|
||||
// ... null-terminated code ...
|
||||
// (with a next line address of 0000 indicating end of program)ß
|
||||
if(files.front().type != File::RelocatableProgram) is_basic = false;
|
||||
else
|
||||
{
|
||||
uint16_t line_address = 0;
|
||||
int line_number = -1;
|
||||
|
||||
uint16_t starting_address = files.front().starting_address;
|
||||
line_address = starting_address;
|
||||
is_basic = false;
|
||||
while(1)
|
||||
{
|
||||
if(line_address - starting_address >= files.front().data.size() + 2) break;
|
||||
|
||||
uint16_t next_line_address = files.front().data[line_address - starting_address];
|
||||
next_line_address |= files.front().data[line_address - starting_address + 1] << 8;
|
||||
|
||||
if(!next_line_address)
|
||||
{
|
||||
is_basic = true;
|
||||
break;
|
||||
}
|
||||
if(next_line_address < line_address + 5) break;
|
||||
|
||||
if(line_address - starting_address >= files.front().data.size() + 5) break;
|
||||
uint16_t next_line_number = files.front().data[line_address - starting_address + 2];
|
||||
next_line_number |= files.front().data[line_address - starting_address + 3] << 8;
|
||||
|
||||
if(next_line_number <= line_number) break;
|
||||
|
||||
line_number = (uint16_t)next_line_number;
|
||||
line_address = next_line_address;
|
||||
}
|
||||
}
|
||||
target.tapes = tapes;
|
||||
|
||||
target.vic20.memory_model = Vic20MemoryModel::Unexpanded;
|
||||
if(is_basic)
|
||||
if(files.front().is_basic())
|
||||
{
|
||||
target.loadingCommand = "LOAD\"\",1,0\nRUN\n";
|
||||
|
||||
// make a first guess based on file size
|
||||
size_t file_size = files.front().data.size();
|
||||
if(file_size > 6655) target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
|
||||
else if(file_size > 3583) target.vic20.memory_model = Vic20MemoryModel::EightKB;
|
||||
else target.vic20.memory_model = Vic20MemoryModel::Unexpanded;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: this is machine code. So, ummm?
|
||||
printf("Need to deal with machine code from %04x to %04x???\n", files.front().starting_address, files.front().ending_address);
|
||||
target.loadingCommand = "LOAD\"\",1,1\nRUN\n";
|
||||
|
||||
// make a first guess based on loading address
|
||||
switch(files.front().starting_address)
|
||||
{
|
||||
@ -96,30 +67,49 @@ void StaticAnalyser::Commodore::AddTargets(
|
||||
target.vic20.memory_model = Vic20MemoryModel::EightKB;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// An unexpanded machine has 3583 bytes free for BASIC;
|
||||
// a 3kb expanded machine has 6655 bytes free.
|
||||
|
||||
// we'll be relocating though, so up size if necessary
|
||||
size_t file_size = files.front().data.size();
|
||||
if(file_size > 6655)
|
||||
// General approach: increase memory size conservatively such that the largest file found will fit.
|
||||
for(File &file : files)
|
||||
{
|
||||
size_t file_size = file.data.size();
|
||||
//bool is_basic = file.is_basic();
|
||||
|
||||
/*if(is_basic)
|
||||
{
|
||||
target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
|
||||
}
|
||||
else if(file_size > 3583)
|
||||
{
|
||||
if(target.vic20.memory_model == Vic20MemoryModel::Unexpanded)
|
||||
// BASIC files may be relocated, so the only limit is size.
|
||||
//
|
||||
// An unexpanded machine has 3583 bytes free for BASIC;
|
||||
// a 3kb expanded machine has 6655 bytes free.
|
||||
if(file_size > 6655)
|
||||
target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
|
||||
else if(target.vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583)
|
||||
target.vic20.memory_model = Vic20MemoryModel::EightKB;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: this is machine code. So, ummm?
|
||||
printf("Need to deal with machine code from %04x to %04x???\n", files.front().starting_address, files.front().ending_address);
|
||||
target.loadingCommand = "LOAD\"\",1,1\nRUN\n";
|
||||
}
|
||||
else
|
||||
{*/
|
||||
// if(!file.type == File::NonRelocatableProgram)
|
||||
// {
|
||||
// Non-BASIC files may be relocatable but, if so, by what logic?
|
||||
// Given that this is unknown, take starting address as literal
|
||||
// and check against memory windows.
|
||||
//
|
||||
// (ignoring colour memory...)
|
||||
// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
|
||||
// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000.
|
||||
// A 32kb expanded Vic has memory in the entire low 32kb.
|
||||
uint16_t starting_address = file.starting_address;
|
||||
|
||||
target.tapes = tapes;
|
||||
// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
|
||||
// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
|
||||
if(starting_address + file_size > 0x2000)
|
||||
target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
|
||||
else if(target.vic20.memory_model == Vic20MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
|
||||
target.vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
50
StaticAnalyser/Commodore/File.cpp
Normal file
50
StaticAnalyser/Commodore/File.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
//
|
||||
// File.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "File.hpp"
|
||||
|
||||
bool StaticAnalyser::Commodore::File::is_basic()
|
||||
{
|
||||
// BASIC files are always relocatable (?)
|
||||
if(type != File::RelocatableProgram) return false;
|
||||
|
||||
uint16_t line_address = starting_address;
|
||||
int line_number = -1;
|
||||
|
||||
// decide whether this is a BASIC file based on the proposition that:
|
||||
// (1) they're always relocatable; and
|
||||
// (2) they have a per-line structure of:
|
||||
// [4 bytes: address of start of next line]
|
||||
// [4 bytes: this line number]
|
||||
// ... null-terminated code ...
|
||||
// (with a next line address of 0000 indicating end of program)ß
|
||||
while(1)
|
||||
{
|
||||
if(line_address - starting_address >= data.size() + 2) break;
|
||||
|
||||
uint16_t next_line_address = data[line_address - starting_address];
|
||||
next_line_address |= data[line_address - starting_address + 1] << 8;
|
||||
|
||||
if(!next_line_address)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if(next_line_address < line_address + 5) break;
|
||||
|
||||
if(line_address - starting_address >= data.size() + 5) break;
|
||||
uint16_t next_line_number = data[line_address - starting_address + 2];
|
||||
next_line_number |= data[line_address - starting_address + 3] << 8;
|
||||
|
||||
if(next_line_number <= line_number) break;
|
||||
|
||||
line_number = (uint16_t)next_line_number;
|
||||
line_address = next_line_address;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
36
StaticAnalyser/Commodore/File.hpp
Normal file
36
StaticAnalyser/Commodore/File.hpp
Normal file
@ -0,0 +1,36 @@
|
||||
//
|
||||
// File.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/09/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef File_hpp
|
||||
#define File_hpp
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace StaticAnalyser {
|
||||
namespace Commodore {
|
||||
|
||||
struct File {
|
||||
std::wstring name;
|
||||
std::vector<uint8_t> raw_name;
|
||||
uint16_t starting_address;
|
||||
uint16_t ending_address;
|
||||
enum {
|
||||
RelocatableProgram,
|
||||
NonRelocatableProgram,
|
||||
DataSequence,
|
||||
} type;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
bool is_basic();
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* File_hpp */
|
@ -11,23 +11,11 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "File.hpp"
|
||||
|
||||
namespace StaticAnalyser {
|
||||
namespace Commodore {
|
||||
|
||||
struct File {
|
||||
std::wstring name;
|
||||
std::vector<uint8_t> raw_name;
|
||||
uint16_t starting_address;
|
||||
uint16_t ending_address;
|
||||
enum {
|
||||
RelocatableProgram,
|
||||
NonRelocatableProgram,
|
||||
DataSequence,
|
||||
} type;
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
|
||||
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ CommodoreTAP::~CommodoreTAP()
|
||||
fclose(_file);
|
||||
}
|
||||
|
||||
void CommodoreTAP::reset()
|
||||
void CommodoreTAP::virtual_reset()
|
||||
{
|
||||
fseek(_file, 0x14, SEEK_SET);
|
||||
_current_pulse.type = Pulse::High;
|
||||
@ -70,7 +70,7 @@ bool CommodoreTAP::is_at_end()
|
||||
return _is_at_end;
|
||||
}
|
||||
|
||||
Storage::Tape::Tape::Pulse CommodoreTAP::get_next_pulse()
|
||||
Storage::Tape::Tape::Pulse CommodoreTAP::virtual_get_next_pulse()
|
||||
{
|
||||
if(_is_at_end)
|
||||
{
|
||||
|
@ -33,11 +33,12 @@ class CommodoreTAP: public Tape {
|
||||
};
|
||||
|
||||
// implemented to satisfy @c Tape
|
||||
Pulse get_next_pulse();
|
||||
void reset();
|
||||
bool is_at_end();
|
||||
|
||||
private:
|
||||
void virtual_reset();
|
||||
Pulse virtual_get_next_pulse();
|
||||
|
||||
FILE *_file;
|
||||
bool _updated_layout;
|
||||
uint32_t _file_size;
|
||||
|
@ -74,7 +74,7 @@ PRG::~PRG()
|
||||
if(_file) fclose(_file);
|
||||
}
|
||||
|
||||
Storage::Tape::Tape::Pulse PRG::get_next_pulse()
|
||||
Storage::Tape::Tape::Pulse PRG::virtual_get_next_pulse()
|
||||
{
|
||||
// these are all microseconds per pole
|
||||
static const unsigned int leader_zero_length = 179;
|
||||
@ -100,7 +100,7 @@ Storage::Tape::Tape::Pulse PRG::get_next_pulse()
|
||||
return pulse;
|
||||
}
|
||||
|
||||
void PRG::reset()
|
||||
void PRG::virtual_reset()
|
||||
{
|
||||
_bitPhase = 3;
|
||||
fseek(_file, 2, SEEK_SET);
|
||||
|
@ -35,11 +35,12 @@ class PRG: public Tape {
|
||||
};
|
||||
|
||||
// implemented to satisfy @c Tape
|
||||
Pulse get_next_pulse();
|
||||
void reset();
|
||||
bool is_at_end();
|
||||
|
||||
private:
|
||||
Pulse virtual_get_next_pulse();
|
||||
void virtual_reset();
|
||||
|
||||
FILE *_file;
|
||||
uint16_t _load_address;
|
||||
uint16_t _length;
|
||||
|
@ -102,7 +102,7 @@ UEF::~UEF()
|
||||
|
||||
#pragma mark - Public methods
|
||||
|
||||
void UEF::reset()
|
||||
void UEF::virtual_reset()
|
||||
{
|
||||
gzseek(_file, 12, SEEK_SET);
|
||||
_is_at_end = false;
|
||||
@ -114,7 +114,7 @@ bool UEF::is_at_end()
|
||||
return _is_at_end;
|
||||
}
|
||||
|
||||
Storage::Tape::Tape::Pulse UEF::get_next_pulse()
|
||||
Storage::Tape::Tape::Pulse UEF::virtual_get_next_pulse()
|
||||
{
|
||||
Pulse next_pulse;
|
||||
|
||||
|
@ -35,11 +35,12 @@ class UEF : public Tape {
|
||||
};
|
||||
|
||||
// implemented to satisfy @c Tape
|
||||
Pulse get_next_pulse();
|
||||
void reset();
|
||||
bool is_at_end();
|
||||
|
||||
private:
|
||||
void virtual_reset();
|
||||
Pulse virtual_get_next_pulse();
|
||||
|
||||
gzFile _file;
|
||||
unsigned int _time_base;
|
||||
bool _is_at_end;
|
||||
|
@ -11,15 +11,38 @@
|
||||
|
||||
using namespace Storage::Tape;
|
||||
|
||||
void Storage::Tape::Tape::seek(Time seek_time)
|
||||
{
|
||||
// TODO: as best we can
|
||||
}
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
TapePlayer::TapePlayer(unsigned int input_clock_rate) :
|
||||
TimedEventLoop(input_clock_rate)
|
||||
{}
|
||||
|
||||
#pragma mark - Seeking
|
||||
|
||||
void Storage::Tape::Tape::seek(Time &seek_time)
|
||||
{
|
||||
_current_time.set_zero();
|
||||
_next_time.set_zero();
|
||||
while(_next_time < seek_time) get_next_pulse();
|
||||
}
|
||||
|
||||
void Storage::Tape::Tape::reset()
|
||||
{
|
||||
_current_time.set_zero();
|
||||
_next_time.set_zero();
|
||||
virtual_reset();
|
||||
}
|
||||
|
||||
Tape::Pulse Tape::get_next_pulse()
|
||||
{
|
||||
Tape::Pulse pulse = virtual_get_next_pulse();
|
||||
_current_time = _next_time;
|
||||
_next_time += pulse.length;
|
||||
return pulse;
|
||||
}
|
||||
|
||||
#pragma mark - Player
|
||||
|
||||
void TapePlayer::set_tape(std::shared_ptr<Storage::Tape::Tape> tape)
|
||||
{
|
||||
_tape = tape;
|
||||
|
@ -38,12 +38,31 @@ class Tape {
|
||||
Pulse() {}
|
||||
};
|
||||
|
||||
virtual Pulse get_next_pulse() = 0;
|
||||
/*!
|
||||
If at the start of the tape returns the first stored pulse. Otherwise advances past
|
||||
the last-returned pulse and returns the next.
|
||||
|
||||
virtual void reset() = 0;
|
||||
@returns the pulse that begins at the current cursor position.
|
||||
*/
|
||||
Pulse get_next_pulse();
|
||||
|
||||
/// Returns the tape to the beginning.
|
||||
void reset();
|
||||
|
||||
/// @returns @c true if the tape has progressed beyond all recorded content; @c false otherwise.
|
||||
virtual bool is_at_end() = 0;
|
||||
|
||||
virtual void seek(Time seek_time); // TODO
|
||||
/// @returns the amount of time preceeding the most recently-returned pulse.
|
||||
virtual Time get_current_time() { return _current_time; }
|
||||
|
||||
/// Advances or reverses the tape to the last time before or at @c time from which a pulse starts.
|
||||
virtual void seek(Time &time);
|
||||
|
||||
private:
|
||||
Time _current_time, _next_time;
|
||||
|
||||
virtual Pulse virtual_get_next_pulse() = 0;
|
||||
virtual void virtual_reset() = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
Loading…
x
Reference in New Issue
Block a user