To be able to run PRGs (programs) and D64s (disk images), we will need to be able to load them. Usually, we would implement the 1541 Floppy Drive now, but we will postpone that for later, since this will take some time (and decoding).
PRG
To load PRGs, we take the input file, and check the first two bytes in little Endian order, to see the location in memory, where the PRGs wants to be loaded to.
void loadPRG(string f) {
uint8_t buf[1];
std::vector<uint8_t> d;
FILE* file = fopen(f.c_str(), "rb");
while (fread(&buf[0], sizeof(uint8_t), 1, file)) {
d.push_back(buf[0]);
}
fclose(file);
printf("d size %x - %d\n", d.size(), d.size());
for (uint16_t i = 2; i < d.size(); i++) {
memory[((d.at(1) << 8) | d.at(0)) - 2 + i] = d.at(i);
}
setTitle(f);
}
Once we have a PRG loaded to its location, all we need to do is “RUN“, to execute it.
D64
The D64 images consist of multiple PRGs at best. So, we will need to be able to pars the D64 format, to be able to read the PRGs the D64 offers. Using LIST will show the directory listing of the currently loaded D64.
I really recommend using D64 Editor (http://www.d64editor.com) to create Menus for your own D64 images.
I made a complete Parser for the D64 file format, which I am going to post here. It works very well so far. Remember, this D64 is being hotloaded, and does not go the usual way through a 1541 Drive (which we will implement later on), so some things are tampered with, so BASIC can properly read it.
#include <string>
#include <vector>
#include <iostream>
using namespace std;
struct Entry {
bool available = false;
uint8_t next_track = 0x00;
uint8_t next_sector = 0x00;
string file_type = "";
uint8_t start_track = 0x00;
uint8_t start_sector = 0x00;
string pet_name = "";
uint32_t adress_start = 0x00;
uint32_t adress_end = 0x00;
uint8_t sector_size = 0x00;
uint8_t sectors = 0x00;
};
/*
Track Sectors/track # Sectors Storage in Bytes
----- ------------- --------- ----------------
1-17 21 357 7820
18-24 19 133 7170
25-30 18 108 6300
31-40(*) 17 85 6020
---
683 (for a 35 track image)
Track #Sect #SectorsIn D64 Offset Track #Sect #SectorsIn D64 Offset
----- ----- ---------- ---------- ----- ----- ---------- ----------
1 21 0 $00000 21 19 414 $19E00
2 21 21 $01500 22 19 433 $1B100
3 21 42 $02A00 23 19 452 $1C400
4 21 63 $03F00 24 19 471 $1D700
5 21 84 $05400 25 18 490 $1EA00
6 21 105 $06900 26 18 508 $1FC00
7 21 126 $07E00 27 18 526 $20E00
8 21 147 $09300 28 18 544 $22000
9 21 168 $0A800 29 18 562 $23200
10 21 189 $0BD00 30 18 580 $24400
11 21 210 $0D200 31 17 598 $25600
12 21 231 $0E700 32 17 615 $26700
13 21 252 $0FC00 33 17 632 $27800
14 21 273 $11100 34 17 649 $28900
15 21 294 $12600 35 17 666 $29A00
16 21 315 $13B00 36(*) 17 683 $2AB00
17 21 336 $15000 37(*) 17 700 $2BC00
18 19 357 $16500 38(*) 17 717 $2CD00
19 19 376 $17800 39(*) 17 734 $2DE00
20 19 395 $18B00 40(*) 17 751 $2EF00
*/
struct D64Parser {
std::vector<string> FILE_TYPE;
unsigned char data[0xf0000];
string filename;
string diskname;
std::vector<Entry> entries;
const uint32_t STARTS[41] = {
0,
0x00000, 0x01500, 0x02a00, 0x03f00, 0x05400, 0x06900, 0x07e00, 0x09300,
0x0a800, 0x0bd00, 0x0d200, 0x0e700, 0x0fc00, 0x11100, 0x12600, 0x13b00,
0x15000, 0x16500, 0x17800, 0x18b00, 0x19e00, 0x1b100, 0x1c400, 0x1d700,
0x1ea00, 0x1fc00, 0x20e00, 0x22000, 0x23200, 0x24400, 0x25600, 0x26700,
0x27800, 0x28900, 0x29a00, 0x2ab00, 0x2bc00, 0x2cd00, 0x2de00, 0x2ef00
};
void init(string f) {
FILE_TYPE.push_back("DEL");
FILE_TYPE.push_back("SEQ");
FILE_TYPE.push_back("PRG");
FILE_TYPE.push_back("USR");
FILE_TYPE.push_back("REL");
entries.clear();
filename = f;
FILE* file = fopen(filename.c_str(), "rb");
uint32_t pos = 0;
while (fread(&data[pos], 1, 1, file)) {
pos++;
}
fclose(file);
// init all available lines
uint32_t base_dir = 0x16600;
for (uint16_t i = 0; i < 0x1200; i += 0x20) {
Entry entry;
entry.next_track = data[base_dir + i];
entry.next_sector = data[base_dir + i + 1];
entry.file_type = (data[base_dir + i + 2] > 0x80 && data[base_dir + i + 2] < 0x84) ? FILE_TYPE.at(data[base_dir + i + 2] - 0x80) : "";
entry.start_track = data[base_dir + i + 3];
entry.start_sector = data[base_dir + i + 4];
entry.pet_name;
for (uint16_t j = 5; j < 0x15; j++) {
entry.pet_name += data[base_dir + i + j];
}
entry.sector_size = (uint8_t)(data[base_dir + i + 0x1e] + (data[base_dir + i + 0x1f] * 256));
entry.available = (entry.file_type != "");
entry.sectors = 21;
if (entry.start_track > 17 && entry.start_track < 25)
entry.sectors = 19;
else if (entry.start_track > 24 && entry.start_track < 31)
entry.sectors = 18;
else if (entry.start_track > 30)
entry.sectors = 17;
entry.adress_start = STARTS[entry.start_track] + entry.start_sector * 256;
entry.adress_end = entry.adress_start + entry.sector_size * 256;
entries.push_back(entry);
}
diskname = "";
for (uint8_t i = 0x90; i < 0xa0; i++) {
diskname += data[STARTS[18] + i];
}
}
std::vector<uint8_t> dirList() {
uint8_t lc = 0x1f;
uint8_t lh = 0x08;
std::vector<uint8_t> r{
0x1f, 0x08, 0x00
};
r.push_back(0x00); // HEADLINE 0
r.push_back(0x12); // HEADLINE BOLD ?
r.push_back(0x22); // "
for (uint16_t i = 0; i < diskname.size(); i++) {
r.push_back(diskname.at(i));
}
r.push_back(0x22); // "
for (uint16_t i = 0; i < 24 - diskname.size() - 2; i++) {
r.push_back(0x20); // FILLING SPACES
}
r.push_back(0x00);
for (uint16_t i = 0; i < entries.size(); i++) {
if (entries[i].available) {
if ((lc + 0x20) > 0xff)
lh++;
lc += 0x20;
r.push_back(lc); // Line separation
r.push_back(lh); // Line separation
r.push_back(entries[i].sector_size); // SIZE
r.push_back(0x00);
string f = std::to_string(entries[i].sector_size);
for (uint16_t j = 0; j < 4 - f.size(); j++) {
r.push_back(0x20); // SPACE
}
r.push_back(0x22); // "
for (uint16_t j = 0; j < entries[i].pet_name.size(); j++) {
r.push_back(entries[i].pet_name.at(j)); // NAME
}
r.push_back(0x22); // "
r.push_back(0x20); // SPACE
for (uint16_t j = 0; j < entries[i].file_type.size(); j++) {
r.push_back(entries[i].file_type.at(j)); // FILE_EXT
}
for (uint16_t j = 0; j < 26 - entries[i].pet_name.size() - 5 - (4 - f.size()); j++) {
r.push_back(0x20); // FILLING SPACES
}
r.push_back(0x00);
}
}
r.push_back(0x1d);
return r;
}
std::vector<uint8_t> getData(uint8_t row_id) {
std::vector<uint8_t> ret(entries.at(row_id).sector_size * 0x100);
uint16_t c = 0;
uint32_t next_track = entries.at(row_id).start_track;
uint32_t next_sector = entries.at(row_id).start_sector;
while(c < entries.at(row_id).sector_size) {
uint32_t a_adr = STARTS[next_track] + next_sector * 256;
for (uint16_t i = 0; i < 254; i++) {
ret[c * 254 + i] = data[a_adr + i + 2];
}
next_track = data[a_adr];
next_sector = data[a_adr + 1];
c++;
}
return ret;
}
bool filenameExists(string f) {
for (uint16_t i = 0; i < entries.size(); i++) {
cout << entries.at(i).pet_name << " - " << f << "\n";
if (entries.at(i).pet_name == f)
return true;
}
return false;
}
std::vector<uint8_t> getDataByFilename(string f) {
Entry selected;
for (uint16_t i = 0; i < entries.size(); i++) {
if (entries.at(i).pet_name == f)
selected = entries.at(i);
}
uint16_t c = 0;
std::vector<uint8_t> ret(selected.sector_size * 0x100);
uint32_t next_track = selected.start_track;
uint32_t next_sector = selected.start_sector;
while (c < selected.sector_size) {
uint32_t a_adr = STARTS[next_track] + next_sector * 256;
for (uint16_t i = 0; i < 254; i++) {
ret[c * 254 + i] = data[a_adr + i + 2];
}
next_track = data[a_adr];
next_sector = data[a_adr + 1];
c++;
}
return ret;
}
void printAll() {
cout << "\t\t" << diskname << "\n";
for (uint16_t i = 0; i < entries.size(); i ++) {
if (entries.at(i).available) {
cout << entries.at(i).file_type << "\t";
cout << hex << +entries.at(i).start_track;
cout << "/";
cout << hex << +entries.at(i).start_sector << "\t";
cout << entries.at(i).pet_name << "\t" << dec << entries.at(i).sector_size;
cout << "\t" << hex << entries.at(i).adress_start << " -> " << hex << entries.at(i).adress_end << "\n";
}
}
}
};
With the given D64Parser, we need to implement a few KERNAL Hooks. Those are needed so we can intercept the SETNAM and the LOAD … ,8,1 commands, the get the actual filename (for the UI) and load the appropriate PRG into memory.
These hooks will be useless, once we have a proper drive emulation.
if (memtype_e000_ffff == MEMTYPE::KERNAL) {
/*
KERNAL HOOKS
*/
// SETNAM hook
if (adr == 0xffbd) {
LOAD_FILE = "";
printf("SETTING NAME y: %x x: %x\n", getCPURegs().Y, getCPURegs().X);
for (int i = 0; i < 16; i++) {
LOAD_FILE += memory[((getCPURegs().Y << 8) | getCPURegs().X) + i];
cout << LOAD_FILE << "<-- FILENAME";
}
}
// LOAD ,8,1 HOOK
if (adr == 0xf52e || adr == 0xf4de) {
if (parser.filenameExists(LOAD_FILE)) {
clearCarry(); // File exists
cout << "File exists " << LOAD_FILE << "\n";
loadPRGFromDisk(LOAD_FILE);
}
else {
setCarry(); // File does not exist
cout << "File does not exist " << LOAD_FILE << "\n";
}
}
return kernal[(adr % 0xe000) & 0x1fff];
}
Now, we can actually load D64s and load PRGs from those disk images.
Nice, but if you load a basic program, you have to update the $002D-$002E memory locations to the end of the program+1 (that’s the variable space).