1 /**
2  * RAM dump loader/saver
3  */
4 module dcpu.ram_io;
5 
6 import core.stdc.stdlib, std.stdio, std.bitmanip, std.conv, std.array, std..string;
7 
8 /// Type of machine code file
9 enum TypeHexFile {
10   lraw,   /// Little-endian raw binary file
11   braw,   /// Big-endian raw binary file
12   ahex,   /// Hexadecimal ASCII file
13   hexd,   /// Hexadecimal ASCII dump file
14   b2,     /// Base 2 binary data (0bxxxxxxxx_xxxxxxxx)
15   dat     /// Assembly DATs (DAT 0x0000)
16   }
17 
18 /**
19  * Load a file with a image of a RAM
20  * Params:
21  *  type = Type of file
22  *  file = Name and path of the file
23  * Returns a array with a raw binary image of the file
24  */
25 ushort[] load_ram(TypeHexFile type, const string filename )
26 in {
27   assert (filename.length >0, "Invalid filename");
28 } body {
29   auto f = File(filename, "r");
30   scope(exit) {f.close();}
31 
32   ushort[] img = new ushort[0];
33 
34   ulong i;
35   if (type == TypeHexFile.lraw || type == TypeHexFile.braw) { // RAW binary file
36     while (i < 0x10000 && !f.eof) {
37       ubyte[2] buffer = [0, 0];
38       if (f.rawRead(buffer).length == 0) {
39         break; // EOF
40       }
41 
42       if (type == TypeHexFile.lraw) { // little-endian
43         img ~= littleEndianToNative!ushort(buffer);
44       } else {
45         img ~= bigEndianToNative!ushort(buffer);
46       }
47       i++;
48     }
49 
50   } else if (type == TypeHexFile.ahex) { // plain ASCII hex file
51     foreach ( line; f.byLine()) { // each line only have a hex 16-bit word
52       if (i >= 0x1000)
53         break;
54       img ~= parse!ushort(line, 16);
55       i++;
56     }
57   } else if (type == TypeHexFile.hexd) { // plain ASCII hex dump file
58     foreach ( line; f.byLine()) { // each line contains one or more words of 16 bit in hexadecimal
59       if (line.length < 1 || line[0] == ';') {
60         continue; // Skip line, becasue it's a comment
61       }
62 
63       auto words = split(strip(line));
64       if (words.length < 2 || words[0].length < 4) {
65         throw new Exception("Bad format. Expected Addr: hexdata -> " ~ cast(string)line );
66       }
67 
68       if (words[0][0..2] == "0x" || words[0][0..2] == "0X")
69         words[0] = words[0][2..$];
70       ushort addr = parse!ushort(words[0], 16);
71 
72       i=0;
73       foreach (word; words[1..$]) {
74         auto tmp = addr + i++;
75         if (tmp >= 0x10000) // Out of bounds
76           throw new Exception("Bad format. Data out of bounds " ~ format("0x%04X", tmp));
77 
78         if (img.length <= tmp)
79           img.length = cast(size_t)(tmp +1);
80 
81         if (word.length > 3) {
82           img[cast(size_t)(tmp)] = parse!ushort(word, 16);
83         }
84       }
85     }
86   } else if (type == TypeHexFile.b2) { // plain ASCII list of numbers in base 2 (0bxxxxxxxx_xxxxxxxx)
87     import std.algorithm;
88     foreach ( line; f.byLine()) { // each line contains one or more words of 16 bit in hexadecimal
89       // Keep alone the number in base 2
90       line = chompPrefix(chompPrefix(line.strip(' '), "0B"), "0b");
91       if (line.length < 16 ) {
92         continue; // Skip line because it's a bad line (ushort -> 16 bits)
93       }
94       auto r = findSplit(line, ['_']);
95       line = r[0] ~ r[2]; // Skips '_'
96 
97       img ~= parse!ushort(line, 2);
98     }
99   } else if (type == TypeHexFile.dat) { // assembly file that contains dat lines with code. Only process DAT lines
100     foreach ( line; f.byLine()) {
101       line = strip(line);
102       if (line.length <= 0) {
103         continue;
104       }
105       if (line[0] == '.') { // Handle the '.'
106         line = line[1..$];
107       }
108       // dat dddd|0xhhhh
109       if (line.length < 5 || (
110             line[0..3] != "dat" && line[0..3] != "DAT" ) ) {
111         continue; // Skip line
112       }
113 
114       auto words = split(line[4..$], ",");
115       if (words.length < 1 ) {
116         throw new Exception("Bad format. DAT without data");
117       }
118 
119       foreach (word; words) {
120         if (img.length >= 0x10000) // Out of bounds
121           throw new Exception("Bad format. Data out of bounds " ~ format("0x%04X", img.length));
122         word = strip(word);
123         if (word.length > 3 && (word[0..2] == "0x" || word[0..2] == "0X")) {
124           word = word[2..$];
125           img ~= parse!ushort(word, 16);
126         } else if (word.length > 1) {
127           img ~= parse!ushort(word, 10);
128         }
129       }
130     }
131   } else {
132     throw new Exception("Not implemented file type");
133   }
134 
135   return img;
136 }
137 
138 void save_ram(TypeHexFile type, const string filename , ushort[] img)
139 in {
140   assert (filename.length >0, "Invalid filename");
141   assert (img.length < 0x10000, "Invalid ram image");
142 } body {
143   auto f = File(filename, "w");
144   scope(exit) {f.close();}
145 
146   if (type == TypeHexFile.lraw || type == TypeHexFile.braw) { // RAW binary file
147     foreach (word; img) {
148       ubyte[2] dbyte = void;
149       if (type == TypeHexFile.lraw) { // little-endian
150         dbyte = nativeToLittleEndian!ushort(word);
151       } else {
152         dbyte = nativeToBigEndian!ushort(word);
153       }
154       f.rawWrite(dbyte);
155     }
156   } else if (type == TypeHexFile.ahex) { // plain ASCII hex file
157     foreach ( word; img) { // each line only have a hex 16-bit word
158       f.writeln(format("%04X", word));
159     }
160   } else if (type == TypeHexFile.hexd) { // plain ASCII hex dump file
161     foreach (addr ,word; img) {
162       if ((addr % 8) == 0) {
163         f.write(format("0x%04X: ", addr));
164       }
165       f.write(format("%04X ", word));
166       if (addr > 6 && ((addr+1) % 8) == 0) {
167         f.writeln();
168       }
169     }
170   } else if (type == TypeHexFile.b2) { // plain ASCII list of numbers in base 2 (0bxxxxxxxx_xxxxxxxx)
171     foreach (word; img) {
172       f.writeln(format("0b%b, ", word));
173     }
174   } else if (type == TypeHexFile.dat) { // assembly file that contains dat lines with code. Only process DAT lines
175     foreach (word; img) {
176       f.writeln(format("DAT %04X ", word));
177     }
178   } else {
179     throw new Exception("Not implemented file type");
180   }
181 }
182