1 package com.tapina.robe.swi;
2
3 import com.tapina.robe.module.Module;
4 import com.tapina.robe.module.ModuleUtils;
5 import com.tapina.robe.runtime.*;
6 import com.tapina.robe.runtime.instruction.SWI;
7 import com.tapina.robe.swi.os.*;
8
9 import java.awt.*;
10 import java.awt.image.BufferedImage;
11 import java.io.*;
12 import java.lang.reflect.Method;
13 import java.text.CharacterIterator;
14 import java.util.*;
15 import java.util.logging.Logger;
16
17 /***
18 * Created by IntelliJ IDEA.
19 * User: gareth
20 * Date: Aug 21, 2003
21 * Time: 6:12:53 PM
22 */
23 public final class OS extends SWIHandler {
24 private static OS instance = null;
25
26 private OS() {
27 }
28
29 public synchronized static OS getInstance() {
30 if (instance == null) {
31 instance = new OS();
32 }
33 return instance;
34 }
35
36 public static int getBase() {
37 return 0;
38 }
39
40 public String getName() {
41 return "UtilityModule";
42 }
43
44 private static final String[] methodNames = new String[]{
45 "WriteC", "", "Write0", "NewLine", "ReadC", "CLI", "Byte", "",
46 "File", "Args", "", "", "GBPB", "Find", "", "",
47 "GetEnv", "Exit", "", "IntOn", "IntOff", "", "EnterOS", "",
48 "", "", "", "", "", "", "Module", "",
49 "", "ReadUnsigned", "", "ReadVarVal", "SetVarVal", "", "", "GSTrans",
50 "", "FSControl", "ChangeDynamicArea", "GenerateError", "", "", "SpriteOp", "",
51 "", "ReadVduVariables", "", "", "", "ReadModeVariable", "", "",
52 "", "", "", "", "", "", "", ""
53 };
54
55 public static Method getMethod(Integer offset) throws NoSuchMethodException {
56 return getMethod(methodNames[offset.intValue()]);
57 }
58
59 private static final Method getMethod(String name) throws NoSuchMethodException {
60 return OS.class.getMethod(name, METHOD_PARAMETERS);
61 }
62
63 public final void WriteC(Environment env) {
64 char charIso88591 = (char) (env.getCpu().R[0] & 0xff);
65 writeC(charIso88591);
66 }
67
68 public final void writeC(char c) {
69 System.out.print(c);
70 }
71
72 public final void NewLine(Environment env) {
73 newLine();
74 }
75
76 public final void newLine() {
77 System.out.print("\r\n");
78 }
79
80 public final void ReadC(Environment env) {
81 char charIso88591 = readC();
82 env.getCpu().clearC();
83 env.getCpu().R[0] = charIso88591 & 0xff;
84 }
85
86 public final char readC() {
87 try {
88 return (char) System.in.read();
89 } catch (IOException e) {
90 throw new SWIError(0, e);
91 }
92 }
93
94 public final void Write0(Environment env) {
95 final int[] R = env.getCpu().R;
96 final String s = env.getMemoryMap().getString0(R[0]);
97 write0(s);
98 R[0] += s.length() + 1;
99 }
100
101 public final void write0(String s) {
102 System.out.print(s);
103 }
104
105 public final void CLI(Environment env) {
106 String command = env.getMemoryMap().getString0_10_13(env.getCpu().R[0]);
107 cli(command);
108 }
109
110 public final void cli(String command) {
111 StringTokenizer tokenizer = new StringTokenizer(command);
112 String id = tokenizer.nextToken();
113 if (id.equalsIgnoreCase("copy")) {
114 final String fromFile = tokenizer.nextToken();
115 final String toFile = tokenizer.nextToken();
116 boolean preserveAccess = true, confirmCopy = true, deleteSource = false, forceOverwrite = false,
117 lookFirst = false, newerOnly = false, promptDiscChange = false, quick = false, recurse = false,
118 restamp = false, structureOnly = false, verbose = true;
119 if (tokenizer.hasMoreTokens()) {
120 final String options = tokenizer.nextToken().toUpperCase();
121 if (options.indexOf("~A") != -1) preserveAccess = false;
122 if (options.indexOf("~C") != -1) confirmCopy = false;
123 if (options.indexOf("D") != -1) deleteSource = true;
124 if (options.indexOf("F") != -1) forceOverwrite = true;
125 if (options.indexOf("L") != -1) lookFirst = true;
126 if (options.indexOf("N") != -1) newerOnly = true;
127 if (options.indexOf("P") != -1) promptDiscChange = true;
128 if (options.indexOf("Q") != -1) quick = true;
129 if (options.indexOf("R") != -1) recurse = true;
130 if (options.indexOf("S") != -1) restamp = true;
131 if (options.indexOf("T") != -1) structureOnly = true;
132 if (options.indexOf("~V") != -1) verbose = false;
133 File in = new File(FilenameUtils.acornToNative(fromFile));
134 File out = new File(FilenameUtils.acornToNative(toFile));
135 BufferedInputStream inputStream = null;
136 BufferedOutputStream outputStream = null;
137 try {
138 inputStream = new BufferedInputStream(new FileInputStream(in));
139 outputStream = new BufferedOutputStream(new FileOutputStream(out));
140 int c;
141 while ((c = inputStream.read()) != -1) {
142 outputStream.write(c);
143 }
144 } catch (IOException e) {
145 throw new SWIError(0, e);
146 } finally {
147 try {
148 if (inputStream != null) {
149 inputStream.close();
150 }
151 if (outputStream != null) {
152 outputStream.close();
153 }
154 } catch (IOException e) {
155
156 }
157 }
158 if (deleteSource) {
159 in.delete();
160 }
161 }
162 } else {
163 log.warning("We do not handle * command: *" + command);
164 }
165 }
166
167 public final void Byte(Environment env) {
168 final int[] R = env.getCpu().R;
169 int[] result = osByte(R[0], R[1], R[2]);
170 System.arraycopy(result, 0, R, 1, result.length);
171 }
172
173 private final static byte[] CMOS = new byte[240];
174
175 static {
176 CMOS[140] = 0;
177 }
178
179 public final int[] osByte(int reasonCode, int r1, int r2) {
180 switch (reasonCode) {
181 case 0:
182 switch (r1) {
183 case 0:
184 throw new SWIError(0, "ROBE 0.10");
185 case 1:
186 return new int[]{6};
187 default:
188 throw new SWIError(0, "Unknown OS.Byte 0," + r1);
189 }
190 case 161:
191 return new int[]{r1, CMOS[r1]};
192 case 240:
193 if (r1 != 0 || r2 != 255) {
194 log.warning("We do not support writing country number with OSByte 240");
195 }
196 return new int[]{Territory.getInstance().getDefaultTerritoryNumber(), 0};
197 default:
198 throw new SWIError(0, "Unknown OS.Byte " + reasonCode);
199 }
200 }
201
202 public final void File(Environment env) {
203 final int[] R = env.getCpu().R;
204 final String filename = env.getMemoryMap().getString0(R[1]);
205 switch (R[0]) {
206 case 1:
207 file1(filename, R[2], R[3], R[5]);
208 break;
209 case 8:
210 file8(filename);
211 break;
212 case 10:
213 ByteArray data = env.getMemoryMap().getByteArray(R[4], R[5] - R[4] + 1, false);
214 file10(filename, R[2], data);
215 break;
216 case 16:
217 if ((R[3] & 0xff) != 0) {
218 throw new SWIError(0, "Cannot load files at their own load address");
219 }
220 if ((R[3] & 1<<31) != 0) {
221 log.warning("We do not correctly synchronise on code load");
222 }
223 final DirectoryEntry entry16 = file5(filename, null);
224 ByteArray buffer = env.getMemoryMap().getByteArray(R[2], entry16.getLength(), true);
225 file16(filename, buffer);
226 break;
227 case 17:
228 DirectoryEntry entry = file5(filename, null);
229 if (entry != null) {
230 R[0] = entry.getObjectType();
231 R[2] = entry.getLoadAddress();
232 R[3] = entry.getExecAddress();
233 R[4] = entry.getLength();
234 R[5] = entry.getAttributes();
235 } else {
236 R[0] = 0;
237 }
238 break;
239 default:
240 throw new SWIError(0, "Unknown OS.File " + R[0]);
241 }
242 }
243
244 /***
245 * This SWI is used to load a named file. An error will be generated if not found, is directory, no read access,
246 * or a bad load address.
247 * @param filename
248 * @param buffer
249 */
250 private void file16(String filename, ByteArray buffer) {
251 final File file = new File(FilenameUtils.acornToNative(filename));
252 if (file.isDirectory()) {
253 throw new SWIError(0, "Filename specified is a directory");
254 }
255 FileInputStream inputStream = null;
256 try {
257 inputStream = new FileInputStream(file);
258 inputStream.read(buffer.getArray(), buffer.getOffset(), buffer.getLength());
259 inputStream.close();
260 } catch (IOException e) {
261 throw new SWIError(0, e);
262 } finally {
263 if (inputStream != null) {
264 try {
265 inputStream.close();
266 } catch (IOException e) {
267 }
268 }
269 }
270 }
271
272 /***
273 * This SWI is used to read catalogue information for a named object.
274 *
275 * @param filename
276 * @param path
277 */
278 public final DirectoryEntry file5(String filename, String path) {
279 if (path != null) {
280 log.warning("OS_File path ignored when reading directory entry");
281 }
282 File file = new File(FilenameUtils.acornToNative(filename));
283 return fileDirectoryEntry(file);
284 }
285
286 public void file1(String filename, int loadAddress, int execAddress, int attributes) {
287 DirectoryEntry directoryEntry = new DirectoryEntry(filename, loadAddress, execAddress, attributes);
288 File file = new File(filename);
289 file.setLastModified(directoryEntry.getTimestamp().getTime());
290 }
291
292 public final void file8(String filename) {
293 final File dir = new File(FilenameUtils.acornToNative(filename));
294 boolean success = dir.isDirectory() || dir.mkdir();
295 if (!success) {
296 throw new SWIError(0, "Unable to create directory and it does not already exist");
297 }
298 }
299
300 public final void file10(String filename, int filetype, ByteArray data) {
301 final File file = new File(FilenameUtils.acornToNative(filename));
302 FileOutputStream outputStream = null;
303 try {
304 outputStream = new FileOutputStream(file);
305 outputStream.write(data.getArray(), data.getOffset(), data.getLength());
306 } catch (IOException e) {
307 throw new SWIError(0, e);
308 } finally {
309 if (outputStream != null) {
310 try {
311 outputStream.close();
312 } catch (IOException e) {
313 }
314 }
315 }
316 }
317
318 public final void Args(Environment env) {
319 final int[] R = env.getCpu().R;
320 int result = args(R[0], R[1] & 0xff, R[2]);
321 R[2] = result;
322 }
323
324 public final int args(int reasonCode, int handle, int attribute) {
325 try {
326 switch (reasonCode) {
327 case 2:
328 RandomAccessFile randomAccessFile = getOpenFile(handle);
329 return (int) randomAccessFile.length();
330 default:
331 throw new SWIError(0, "Unknown OS.Args " + reasonCode);
332 }
333 } catch (IOException e) {
334 throw new SWIError(0, e);
335 }
336 }
337
338 public final void GBPB(Environment env) {
339 final CPU cpu = env.getCpu();
340 final int[] R = cpu.R;
341 final MemoryMap memoryMap = env.getMemoryMap();
342 switch (R[0]) {
343 case 4:
344 final int count = R[3];
345 final byte[] buffer = new byte[count];
346 int unreadCount = gbpb4(R[0], R[1], buffer, 0, count);
347 final int readCount = count - unreadCount;
348 memoryMap.storeBytes(R[2], buffer, 0, readCount);
349 R[2] += readCount;
350 R[3] = unreadCount;
351 if (unreadCount == 0) {
352 cpu.clearC();
353 } else {
354 cpu.setC();
355 }
356 log.warning("New file pointer not returned in R4");
357 break;
358 case 11:
359 final String dirname = memoryMap.getString0(R[1]);
360 final String match = R[6] == 0 ? null : memoryMap.getString0(R[6]);
361 final DirectoryEntry[] entries = gbpb11(R[0], dirname, R[4], R[3], match);
362
363 int entryBase = R[2];
364 R[3] = 0;
365 for (int i = 0; i < entries.length; i++, R[3]++) {
366 DirectoryEntry entry = entries[i];
367 int entrySize = 29 + entry.getName().length() + 1;
368 if (entryBase + entrySize > R[2] + R[5]) {
369 break;
370 }
371 long fiveByteTime = convertTo5ByteTime(entry.getTimestamp());
372 memoryMap.storeWords(entryBase, new int[]{entry.getLoadAddress(), entry.getExecAddress(),
373 entry.getLength(), entry.getAttributes(),
374 entry.getObjectType(), entry.getInternalName(),
375 (int) (fiveByteTime & 0xffffffffL)});
376 memoryMap.storeByte(entryBase + 28, (byte) (fiveByteTime >> 32));
377 memoryMap.storeString0(entryBase + 29, entry.getName());
378 entryBase += (entrySize + 3) & (~3);
379 }
380 R[4] = (entries.length == 0) ? -1 : R[4] + entries.length;
381 if (R[3] == 0) {
382 cpu.clearC();
383 } else {
384 cpu.setC();
385 }
386 break;
387 default:
388 throw new SWIError(0, "Unknown OS.GBPB " + R[0]);
389 }
390 }
391
392 public final int gbpb4(int reasonCode, int fileHandle, byte[] buffer, int offset, int count) {
393 try {
394 switch (reasonCode) {
395 case 4:
396 RandomAccessFile randomAccessFile = getOpenFile(fileHandle);
397 int readCount = randomAccessFile.read(buffer, offset, count);
398 if (readCount != -1) {
399 return count - readCount;
400 } else {
401 return count;
402 }
403 default:
404 throw new SWIError(0, "Unknown OS.GBPB " + reasonCode);
405 }
406 } catch (IOException e) {
407 throw new SWIError(0, e);
408 }
409 }
410
411 private final Map directoryEntryMap = new HashMap(10);
412
413 public final DirectoryEntry[] gbpb11(int reasonCode, String dirName, int offset, int count, String match) {
414 switch (reasonCode) {
415 case 11:
416 final String key = dirName + ':' + match;
417 DirectoryEntry[] entries = (DirectoryEntry[]) directoryEntryMap.get(key);
418 if (entries == null) {
419 File f = new File(FilenameUtils.acornToNative(dirName));
420 if (match != null && !match.equals("*")) {
421 log.warning("Filename match string ignored");
422 }
423 File[] fs = f.listFiles();
424 entries = new DirectoryEntry[fs == null ? 0 : fs.length];
425 for (int i = 0; i < entries.length; i++) {
426 File file = fs[i];
427 entries[i] = fileDirectoryEntry(file);
428 }
429 directoryEntryMap.put(key, entries);
430 }
431 if (entries.length - offset < count) {
432 count = entries.length - offset;
433 }
434 DirectoryEntry[] result = new DirectoryEntry[count];
435 System.arraycopy(entries, offset, result, 0, count);
436 return result;
437 default:
438 throw new SWIError(0, "Unknown OS.GBPB " + reasonCode);
439 }
440 }
441
442 /***
443 * Return a DirectoryEntry for a given File.
444 * DirectoryEntry is a representation of the information RISC OS programs expect to be able to find out about
445 * a file from its directory entry. File is a Java representation of an abstract point in the filesystem.
446 *
447 * @param file filesystem entry to obtain DirectoryEntry for. It need not exist.
448 * @return DirectoryEntry for given file. It will indicate the file's type.
449 */
450 DirectoryEntry fileDirectoryEntry(File file) {
451 long timestamp = file.lastModified();
452 long length = file.length();
453 int attributes = 0;
454 if (file.canRead()) {
455 attributes |= DirectoryEntry.ATTRIBUTE_OWNER_READ + DirectoryEntry.ATTRIBUTE_OTHERS_READ;
456 }
457 if (file.canWrite()) {
458 attributes |= DirectoryEntry.ATTRIBUTE_OWNER_WRITE + DirectoryEntry.ATTRIBUTE_OTHERS_WRITE;
459 }
460 if (file.isHidden()) {
461 attributes |= DirectoryEntry.ATTRIBUTE_HIDDEN + DirectoryEntry.ATTRIBUTE_LOCKED;
462 }
463 final int type = file.exists() ? (file.isDirectory() ? DirectoryEntry.TYPE_DIRECTORY : DirectoryEntry.TYPE_FILE) :
464 DirectoryEntry.TYPE_NOT_FOUND;
465 return new DirectoryEntry(FilenameUtils.nativeToAcorn(file.getName()),
466 new Date(timestamp), length, attributes, type);
467 }
468
469 public final void Find(Environment env) {
470 final int[] R = env.getCpu().R;
471 final int action = R[0] & 0xc0;
472 final int pathType = R[0] & 0x03;
473 boolean dirError = (R[0] & 0x04) != 0;
474 boolean missingError = (R[0] & 0x08) != 0;
475 if (action != 0) {
476 final String filename = env.getMemoryMap().getString0(R[1]);
477 final String path;
478 switch (pathType) {
479 case 0:
480 path = readVarVal("File$Path");
481 break;
482 case 1:
483 path = env.getMemoryMap().getString0(R[2]);
484 break;
485 case 2:
486 path = readVarVal(env.getMemoryMap().getString0(R[2]));
487 break;
488 default:
489 path = null;
490 }
491 R[0] = find(action, filename, path, dirError, missingError);
492 } else {
493 find(action, R[1]);
494 }
495 }
496
497 private static int fileHandleSeq = 100;
498 private final Map randomAccessFileMap = new HashMap(10);
499
500 public final int find(int action, String filename, String path, boolean dirError, boolean missingError) {
501 if (path != null) {
502 log.warning("We do not support paths in OS.Find");
503 }
504 filename = FilenameUtils.acornToNative(filename);
505 RandomAccessFile randomAccessFile;
506 try {
507 switch (action) {
508 case 0x40:
509
510 randomAccessFile = new RandomAccessFile(filename, "r");
511 break;
512 case 0x80:
513
514 randomAccessFile = new RandomAccessFile(filename, "rw");
515 randomAccessFile.setLength(0);
516 break;
517 case 0xc0:
518
519 randomAccessFile = new RandomAccessFile(filename, "rw");
520 break;
521 default:
522 throw new SWIError(0, "Unknown OS.Find &" + Integer.toHexString(action));
523 }
524 synchronized (OS.class) {
525 final int fileHandle = fileHandleSeq++;
526 randomAccessFileMap.put(new Integer(fileHandle), randomAccessFile);
527 return fileHandle;
528 }
529 } catch (IOException e) {
530 throw new SWIError(0, e);
531 }
532 }
533
534 public final int find(int action, int handle) {
535 if (action == 0) {
536
537 RandomAccessFile randomAccessFile = getOpenFile(handle);
538 if (randomAccessFile != null) {
539 try {
540 randomAccessFile.close();
541 } catch (IOException e) {
542 throw new SWIError(0, e);
543 }
544 }
545 }
546 return handle;
547 }
548
549 private RandomAccessFile getOpenFile(int handle) {
550 final RandomAccessFile randomAccessFile = (RandomAccessFile) randomAccessFileMap.get(new Integer(handle));
551 if (randomAccessFile == null) {
552 throw new SWIError(0, "Invalid file handle");
553 }
554 return randomAccessFile;
555 }
556
557 static final int DEFAULT_RAM_LIMIT = 1024 * 1024;
558 static final AppEnvironment DEFAULT_APP_ENVIRONMENT = new AppEnvironment("ROBE", DEFAULT_RAM_LIMIT, new Date());
559 private RawDataBlock envStartTime = null;
560 private RawDataBlock envCommandLine = null;
561
562 public final void GetEnv(Environment env) {
563 final int[] R = env.getCpu().R;
564 final MemoryMap memoryMap = env.getMemoryMap();
565
566 AppEnvironment appEnvironment = getEnv();
567 if (envCommandLine == null) {
568 envCommandLine = memoryMap.createSystemDataBlock(appEnvironment.getCommandLine().length() + 1);
569 }
570 ByteArrayUtils.putString0(envCommandLine.getBytes(), 0, appEnvironment.getCommandLine());
571 R[0] = envCommandLine.getAddress();
572
573 log.warning("We do not support getting RAM limit correctly, returning size of &" +
574 Integer.toHexString(appEnvironment.getRamLimit()));
575 R[1] = appEnvironment.getRamLimit();
576
577 if (envStartTime == null) {
578 envStartTime = memoryMap.createSystemDataBlock(5);
579 }
580 ByteArrayUtils.put5ByteValue(envStartTime.getBytes(), 0, convertTo5ByteTime(appEnvironment.getStartTime()));
581 R[2] = envStartTime.getAddress();
582 }
583
584 private final static long EPOCH_BASE = new GregorianCalendar(1900, 0, 1, 0, 0, 0).getTimeInMillis();
585
586 public final static long convertTo5ByteTime(Date date) {
587 long timeMillis = date.getTime();
588 long timeCentis = (timeMillis - EPOCH_BASE) / 10;
589 return timeCentis & 0xffffffffffL;
590 }
591
592 public final static Date convertToDate(long fiveByteTime) {
593 long timeMillis = (fiveByteTime * 10) + EPOCH_BASE;
594 return new Date(timeMillis);
595 }
596
597 public final AppEnvironment getEnv() {
598 return DEFAULT_APP_ENVIRONMENT;
599 }
600
601 public final void Exit(Environment env) {
602 final int[] R = env.getCpu().R;
603 if (R[1] == 0x58454241) {
604 exit(R[2]);
605 } else {
606 exit(0);
607 }
608 }
609
610 public final void exit(int rc) {
611 final long executionTime = (System.currentTimeMillis() - OS.DEFAULT_APP_ENVIRONMENT.getStartTime().getTime());
612 log.info("Executed " + Instruction.count + " instructions in " + executionTime + "ms");
613 log.info(" = " + ((double) Instruction.count * 1000 / ((double) executionTime)) + "/s");
614 System.exit(rc);
615 }
616
617 public final void IntOn(Environment env) {
618 env.getCpu().setI();
619 }
620
621 public final void IntOff(Environment env) {
622 env.getCpu().clearI();
623 }
624
625 public final void EnterOS(Environment env) {
626 log.warning("OS.EnterOS ignored");
627 }
628
629 public final void Module(Environment env) {
630 final int[] R = env.getCpu().R;
631 switch (R[0]) {
632 case 6:
633 R[2] = env.getMemoryMap().claimRmaBlock(R[3]).getAddress();
634 break;
635 case 7:
636 env.getMemoryMap().releaseRmaBlock(R[2]);
637 break;
638 case 18:
639 final String name = env.getMemoryMap().getString0(R[1]);
640 if (name.indexOf('%') > -1) {
641 log.warning("We do not handle modules with multiple instantiations correctly");
642 }
643 final Module module = ModuleUtils.getModule(name);
644 if (module != null) {
645 R[1] = ModuleUtils.getModuleNumber(module);
646 R[2] = 0;
647 R[3] = module.getAddress();
648 R[4] = 0x98765432;
649 R[5] = 0;
650 } else {
651 log.severe("Module not found: " + name);
652 throw new SWIError(0, "Module not found: " + name);
653 }
654 break;
655 default:
656 throw new SWIError(0, "Unknown OS.Module " + R[0]);
657 }
658 }
659
660 public final void ReadUnsigned(Environment env) {
661 final int[] R = env.getCpu().R;
662 int base = R[0] & 0xff;
663 if (base < 2 || base > 36) {
664 base = 10;
665 }
666 if ((R[0] & 0x80000000) != 0) {
667 log.warning("we do not support restriction on terminator in OS.ReadUnsigned - ignoring");
668 }
669 final String s = env.getMemoryMap().getStringN(R[1], 34);
670 final int result = readUnsigned(base, s);
671 if (((R[0] & 0x40000000) != 0 && result > 255)
672 || ((R[0] & 0x2000000) != 0 && result > R[2])) {
673 throw new SWIError(0, "Number out of range");
674 }
675 log.warning("Terminator character pointer is incorrect");
676 R[2] = result;
677 }
678
679 /***
680 * Convert a String to an integer.
681 * If the String starts with &, base 16 is assumed. If the String starts with <var>base</var>_, this base
682 * is used. If no base is given or the base is invalid, the base in defaultInputBase is used.
683 * The terminating character in the string is the first character that is inconsistent with the base
684 * being used.
685 *
686 * @param defaultInputBase base to use if none is specified in the string
687 * @param input String to convert
688 * @return
689 */
690 public final int readUnsigned(int defaultInputBase, String input) {
691 int base = defaultInputBase;
692 int underscoreIndex = input.indexOf('_');
693
694 if (input.startsWith("&")) {
695 base = 16;
696 input = input.substring(1);
697 } else if (underscoreIndex > 0) {
698 final String s = input.substring(0, underscoreIndex);
699 try {
700 base = Integer.parseInt(s);
701 } catch (NumberFormatException e) {
702 log.warning("Invalid base " + s + " in OS.readUnsigned");
703 }
704 input = input.substring(underscoreIndex + 1);
705 }
706
707 if (base < 2 || base > 36) {
708 base = defaultInputBase;
709 }
710
711
712 for (int i = 0; i < input.length(); i++) {
713 final char c = input.charAt(i);
714 final int decimalDigit = c - '0';
715 final int alphaDigit = Character.toUpperCase(c) - 'A' + 10;
716 if ((decimalDigit < 0 || decimalDigit > 9) &&
717 (alphaDigit < 10 || alphaDigit >= base)) {
718
719 input = input.substring(0, i);
720 break;
721 }
722 }
723 try {
724 return (int) (Long.parseLong(input, base) & 0xffffffffL);
725 } catch (NumberFormatException e) {
726 throw new SWIError(0, e);
727 }
728 }
729
730 /***
731 * This SWI returns type and value of a variable.
732 * To check if it exists or find its length, call with R2 < 0. If the variable does not exist R2 will be 0.
733 * Otherwise the length is given by NOT(R2), although for anything other than a string variable (type 0) this is the
734 * unexpanded length. For either case an error will be returned.
735 *
736 * @param env IN: R0 = pointer to wildcarded name
737 * R1 = pointer to buffer
738 * R2 = length of buffer, or -ve to check existence/read length
739 * R3 = name pointer, or 0 for 1st call
740 * R4 = 3 to expand macros and numbers to strings.
741 * OUT: if R2 was -ve (read length)
742 * R0 corrupt (pointer to error)
743 * R2 = NOT (length), or 0 if variable does not exist.
744 * if R2 was +ve (read value)
745 * R0 preserved
746 * R2 = bytes read
747 * In both cases:
748 * R3 = new name pointer
749 * R4 = Variable type
750 */
751 public final void ReadVarVal(Environment env) {
752 final int[] R = env.getCpu().R;
753 final MemoryMap memoryMap = env.getMemoryMap();
754 String value = readVarVal(memoryMap.getString0(R[0]));
755 final int bufferLength = R[2];
756 if (bufferLength < 0) {
757 R[0] = 0;
758 R[2] = value == null ? 0 : ~(value.length());
759 } else {
760 if (bufferLength == 0 || value == null) {
761 value = "";
762 } else if (value.length() >= bufferLength) {
763 value = value.substring(0, bufferLength - 1);
764 }
765 memoryMap.storeString0(R[1], value);
766 R[2] = value.length();
767 }
768 R[3] = 0;
769 R[4] = 0;
770 }
771
772 private final static Properties ENVIRONMENT_VARIABLES = new Properties();
773
774 public final String readVarVal(String varName) {
775 return ENVIRONMENT_VARIABLES.getProperty(varName);
776 }
777
778 /***
779 * This SWI sets a variable's value to that specified, or deletes the variable. The name may be wildcarded for
780 * deletion and update (using '*' and '#'). Code variables will not be deleted unless R4 = 16. Literal strings
781 * do not need to be null terminated, as R2 is used for the length.
782 *
783 * @param env IN: R0 = pointer to name
784 * R1 = pointer to value to set to
785 * R2 = length, or -1 to delete
786 * R3 = name pointer (0 for 1st call)
787 * R4 = variable type (0 = string, 1 = integer, 2 = macro, 3 = expression, 4 = literal string, +16 code)
788 * OUT: R3 = new name pointer
789 * R4 = variable type
790 */
791 public final void SetVarVal(Environment env) {
792 final int[] R = env.getCpu().R;
793 final MemoryMap memoryMap = env.getMemoryMap();
794 if (R[2] != -1) {
795 setVarVal(memoryMap.getString0(R[0]), memoryMap.getStringN(R[1], R[2]), R[4]);
796 } else {
797 deleteVarVal(memoryMap.getString0(R[0]));
798 }
799 }
800
801 public void setVarVal(String name, String value, int valueType) {
802 if (valueType != 0 && valueType != 4) {
803 log.warning("setVarVal always stores variables as strings <" + name + "> = \"" + value + "\"");
804 }
805 ENVIRONMENT_VARIABLES.setProperty(name, value);
806 }
807
808 public void deleteVarVal(String name) {
809 log.warning("setVarVal(deleteVarVal) unimplemented, no value removed for <" + name + ">");
810 }
811
812 /***
813 * This SWI is equivalent to a call to OS_GSInit, followed by repeated calls
814 * to OS_GSRead . It reads and translates a whole string.
815 * <pre><![CDATA[
816 * Translates a string as follows:
817 * From To
818 * |" "
819 * |< <
820 * |! forces top bit of next char to be set.
821 * |char CTRL( ASCII(uppercase(char) - 64 )
822 * "str" str
823 * <num> CTRL(num)
824 * <str> system variable str
825 * ]]>
826 * </pre>
827 *
828 * @param env
829 */
830 public final void GSTrans(Environment env) {
831 final int[] R = env.getCpu().R;
832 String in = env.getMemoryMap().getString0_10_13(R[0]);
833 final boolean spaceTerminates = (R[2] & 0x20000000) != 0;
834 final boolean convertControlCodes = (R[2] & 0x40000000) == 0;
835 final boolean stripQuotes = (R[2] & 0x80000000) == 0;
836 log.warning("convert control codes and strip quotes flags ignored");
837 GSTranslator translator = new GSTranslator(in, spaceTerminates, this);
838 String out = gsTrans(translator, new StringBuffer(in.length()));
839 int bufferLength = R[2] & 0x1fffffff;
840 if (out.length() > bufferLength - 1) {
841 out = out.substring(0, bufferLength - 1);
842 env.getCpu().setC();
843 }
844
845 R[0] += translator.getIndex() + 1;
846 env.getMemoryMap().storeString0(R[1], out);
847 R[2] = out.length();
848 }
849
850 public final String gsTrans(String in, boolean spaceTerminates, boolean convertControlCodes, boolean stripQuotes) {
851 StringBuffer out = new StringBuffer(in.length());
852 GSTranslator translator = new GSTranslator(in, spaceTerminates, this);
853
854 return gsTrans(translator, out);
855 }
856
857 private String gsTrans(GSTranslator translator, StringBuffer out) {
858 do {
859 char c = translator.nextCharacter();
860 if (c == CharacterIterator.DONE) {
861 break;
862 } else {
863 out.append(c);
864 }
865 } while (true);
866 return out.toString();
867 }
868
869 public final void FSControl(Environment env) {
870 final int[] R = env.getCpu().R;
871 final MemoryMap memoryMap = env.getMemoryMap();
872 String pathname = memoryMap.getString0(R[1]);
873 final String path;
874 if (R[3] != 0) {
875 path = readVarVal(memoryMap.getString0(R[3]));
876 } else if (R[4] != 0) {
877 path = memoryMap.getString0(R[4]);
878 } else {
879 path = null;
880 }
881 pathname = fsControl(R[0], pathname, path);
882 final int bufferLength = R[5];
883 if (pathname.length() > bufferLength) {
884 pathname = pathname.substring(0, bufferLength);
885 }
886 memoryMap.storeString0(R[2], pathname);
887 R[5] -= pathname.length();
888 }
889
890 public final String fsControl(int reasonCode, String pathname, String path) {
891 if (path != null) {
892 log.warning("Path ignored in OS.FSControl");
893 }
894 try {
895 switch (reasonCode) {
896 case 37:
897 File f = new File(FilenameUtils.acornToNative(pathname));
898 return FilenameUtils.nativeToAcorn(f.getCanonicalPath());
899 default:
900 throw new SWIError(0, "Unknown OS.FSControl " + reasonCode);
901 }
902 } catch (IOException e) {
903 throw new SWIError(0, e);
904 }
905 }
906
907 public final void ChangeDynamicArea(final Environment env) {
908 final int[] R = env.getCpu().R;
909 if (R[0] < 256) {
910 log.warning("We do not support changing non-user areas.");
911 }
912 final DynamicArea dynamicArea = env.getMemoryMap().findDynamicArea(R[0]);
913 final int changeRequest = R[1];
914 if (changeRequest > 0) {
915 dynamicArea.extendForwards(changeRequest);
916 } else {
917
918 log.info("ChangeDynamicArea will never reduce memory usage!");
919 R[1] = 0;
920 }
921 }
922
923 public final void GenerateError(Environment env) {
924 final int[] R = env.getCpu().R;
925 throw new SWIError(env, R[0]);
926 }
927
928 public final void generateError(int errorNumber, String errorMessage) throws SWIError {
929 throw new SWIError(errorNumber, errorMessage);
930 }
931
932 private static final SpriteArea SYSTEM_SPRITE_AREA = new SpriteArea(0x100000);
933
934 public final void SpriteOp(Environment env) {
935 final int[] R = env.getCpu().R;
936 final SpriteArea spriteArea;
937 final MemoryMap memoryMap = env.getMemoryMap();
938 final String spriteName = memoryMap.getString0(R[2]);
939 if ((R[0] & 0x300) != 0) {
940 final DataBlock dataBlock = memoryMap.getDataBlock(R[1]);
941 if (!(dataBlock instanceof SpriteArea)) {
942 spriteArea = new SpriteArea(dataBlock);
943 memoryMap.replaceDataBlock(dataBlock, spriteArea);
944 } else {
945 spriteArea = (SpriteArea) dataBlock;
946 }
947 if ((R[0] & 0x200) != 0) {
948 log.warning("we do not support sprite pointer arguments to OS.SpriteOp - ignoring");
949 return;
950 }
951 } else {
952 spriteArea = SYSTEM_SPRITE_AREA;
953 }
954 SpriteOp(R, spriteArea, spriteName);
955 }
956
957 void SpriteOp(final int[] R, final SpriteArea spriteArea, final String spriteName) {
958 final int operation = R[0] & 0xff;
959 switch (operation) {
960 case 40:
961 final BufferedImage image = spriteOp40(spriteArea, spriteName);
962 R[3] = image.getWidth();
963 R[4] = image.getHeight();
964 log.warning("All sprites return as 24bpp with 90x90 dpi since we use TYPE_INT_ARGB native images");
965 R[5] = image.getColorModel().hasAlpha()? 1 : 0;
966 R[6] = (6 << 27) | 1 | (90 << 1) | (90 << 14);
967 break;
968 default:
969 spriteOp(operation, spriteArea, spriteName);
970 }
971 }
972
973 public final BufferedImage spriteOp40(SpriteArea spriteArea, String spriteName) {
974 return spriteArea.getSprite(spriteName).getImage();
975 }
976
977 public final void spriteOp(int operation, SpriteArea spriteArea, String spriteName) {
978 switch (operation) {
979 case 10:
980
981 synchronized (spriteArea) {
982 spriteArea.load(new File(FilenameUtils.acornToNative(spriteName)));
983 }
984 break;
985 case 11:
986
987 synchronized (spriteArea) {
988 spriteArea.merge(new File(FilenameUtils.acornToNative(spriteName)));
989 }
990 break;
991 default:
992 spriteOp(operation, spriteArea, spriteArea.getSprite(spriteName));
993 }
994 }
995
996 public final void spriteOp(int operation, SpriteArea spriteArea, Sprite sprite) {
997 log.warning("spriteOp on Sprite unimplemented");
998 }
999
1000 /***
1001 * Entry:
1002 * => R0 = pointer to input block
1003 * R1 = pointer to output block (can be same as R0)
1004 * The input block is a list of variable numbers (words) terminated by -1.
1005 * Each variable is read, and its value is written as a word into the output block.
1006 * Note: You can also read the mode variables with this call.
1007 *
1008 * @param env
1009 */
1010 public final void ReadVduVariables(Environment env) {
1011 final int[] R = env.getCpu().R;
1012 final MemoryMap memoryMap = env.getMemoryMap();
1013 for (int inAddress = R[0], outAddress = R[1]; ; inAddress += 4, outAddress += 4) {
1014 final int number = memoryMap.getWord(inAddress);
1015 if (number == -1) {
1016 break;
1017 }
1018 final int value = readVduVariable(number);
1019 memoryMap.storeWord(outAddress, value);
1020 }
1021 }
1022
1023 public final int readVduVariable(int number) {
1024 final DisplayMode displayMode = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode();
1025 final int bitDepth = displayMode.getBitDepth();
1026 switch (number) {
1027 case 3:
1028 if (bitDepth == 24) {
1029 return -1;
1030 } else {
1031 return (1 << bitDepth) - 1;
1032 }
1033 case 4:
1034 return 1;
1035 case 5:
1036 return 1;
1037 case 9:
1038 if (bitDepth == -1) {
1039 return 5;
1040 } else {
1041 return log2(bitDepth);
1042 }
1043 case 11:
1044 return displayMode.getWidth() - 1;
1045 case 12:
1046 return displayMode.getHeight() - 1;
1047 default:
1048 log.warning("Cannot read VDU variable " + number);
1049 return 0;
1050 }
1051 }
1052
1053 public void ReadModeVariable(Environment env) {
1054 final int[] R = env.getCpu().R;
1055 if (R[0] != -1) {
1056 log.warning("Cannot read mode variable for a different mode: reading for current mode");
1057 }
1058
1059 if (R[1] < 128) {
1060 R[2] = readVduVariable(R[1]);
1061 } else {
1062 log.info("Attempt to read mode variable " + R[1] + " - failed");
1063 env.getCpu().setC();
1064 }
1065 }
1066
1067 private int log2(int v) {
1068 for (int log = 0, mask = 1; log < 32; log++, mask <<= 1) {
1069 if ((v & mask) != 0) {
1070 if ((v & ~(mask - 1)) == mask) {
1071 if ((v & ~mask) == mask) {
1072 return log;
1073 } else {
1074 return log + 1;
1075 }
1076 }
1077 }
1078 }
1079 throw new IllegalArgumentException("Could not resolve log2(" + v + ")");
1080 }
1081
1082 public static void main(String[] args) throws UnknownSWIException {
1083 Logger log = Logger.getLogger(OS.class.getName());
1084 Environment environment = new Environment(0x8000, new byte[0]);
1085 final int[] R = environment.getCpu().R;
1086 Instruction swiCall;
1087
1088 log.info("SWI OS_WriteC");
1089 R[0] = 65;
1090 log.fine("R[0] = " + R[0]);
1091 swiCall = new SWI(Condition.AL, 0x0);
1092 swiCall.checkAndExecute(environment);
1093
1094 R[0] = 0;
1095 R[1] = 0;
1096 log.fine("R[0] = " + R[0]);
1097 log.fine("R[1] = " + R[1]);
1098 log.info("SWI XOS_Byte");
1099 swiCall = new SWI(Condition.AL, 0x800006);
1100 swiCall.checkAndExecute(environment);
1101 log.fine("=> R[0] = " + R[0]);
1102
1103 R[0] = 0;
1104 R[1] = 1;
1105 log.fine("R[0] = " + R[0]);
1106 log.fine("R[1] = " + R[1]);
1107 log.info("SWI OS_Byte");
1108 swiCall.checkAndExecute(environment);
1109 log.fine("=> R[1] = " + R[1]);
1110 }
1111 }