|
|
1.1 root 1: //
2: // nono
3: // Copyright (C) 2020 nono project
4: // Licensed under nono-license.txt
5: //
6:
1.1.1.5 root 7: //
1.1 root 8: // ブランチ履歴
1.1.1.5 root 9: //
1.1 root 10:
11: #include "branchhistory.h"
1.1.1.10 root 12: #include "m680x0disasm.h"
1.1 root 13: #include "m88100.h"
1.1.1.10 root 14: #include "monitor.h"
1.1.1.7 root 15: #include "mpu64180.h"
1.1.1.5 root 16: #include "vectortable.h"
1.1 root 17:
18: // コンストラクタ
1.1.1.9 root 19: BranchHistory::BranchHistory(uint objid_)
1.1.1.7 root 20: : inherited(objid_)
1.1 root 21: {
1.1.1.7 root 22: int monitor_id;
23:
24: switch (GetId()) {
25: case OBJ_MPU_BRHIST:
26: is_exhist = false;
27: monitor_id = ID_SUBWIN_BRHIST;
28: break;
29: case OBJ_MPU_EXHIST:
30: is_exhist = true;
31: monitor_id = ID_SUBWIN_EXHIST;
32: break;
33: case OBJ_XP_BRHIST:
34: is_exhist = false;
35: monitor_id = ID_SUBWIN_XPBRHIST;
36: break;
37: case OBJ_XP_EXHIST:
38: is_exhist = true;
39: monitor_id = ID_SUBWIN_XPEXHIST;
40: break;
41: default:
42: PANIC("unknown id %s", GetIdStr());
43: }
1.1.1.4 root 44:
1.1.1.5 root 45: // ログは不要
46: ClearAlias();
47:
1.1.1.7 root 48: vectortable = GetVectorTable();
49:
1.1.1.10 root 50: monitor = gMonitorManager->Regist(monitor_id, this);
1.1.1.13! root 51: monitor->SetCallback(&BranchHistory::MonitorScreen);
1.1.1.10 root 52: monitor->SetSize(55, 1 + 32); // ヘッダ1行とエントリ数
53: monitor->SetMaxHeight(1 + 256);
1.1.1.7 root 54:
55: // hd64180 はこれを上書きする
56: x_count = 45;
1.1 root 57: }
58:
59: // デストラクタ
60: BranchHistory::~BranchHistory()
61: {
62: }
63:
64: // 初期化
65: void
66: BranchHistory::Clear()
67: {
68: top = 0;
69: for (int i = 0; i < countof(entry); i++) {
70: BranchEntry& e = entry[i];
71: e.count = 0;
72: e.from = 0xffffffff;
73: e.to = 0xffffffff;
1.1.1.11 root 74: e.info = 0;
1.1 root 75: }
76: }
77:
78: // 使用中のエントリ数を取得する
79: int
80: BranchHistory::GetUsed() const
81: {
82: int i = 0;
83:
84: // top の位置から一巡するまで。有効エントリは count > 0。
85: for (; i < 256; i++) {
86: if (entry[(256 + top - i) % 256].count == 0) {
87: break;
88: }
89: }
90: return i;
91: }
92:
93: // モニタ更新
94: void
1.1.1.13! root 95: BranchHistory::MonitorScreen(Monitor *, TextScreen& screen)
1.1 root 96: {
97: uint8 t;
98: int y;
99: int ydelta;
100: int row;
101:
102: // userdata は表示開始位置と各種フラグ。
103: // 下位 32bit が表示開始位置で、0 なら先頭のエントリから、1 なら先頭の
1.1.1.4 root 104: // 一つ次のエントリから、…を表す (表示行数は screen の高さ分)。
1.1 root 105: // BottomToTop が %1 なら並び順を下から上に変える (コンソール用)。
1.1.1.4 root 106: bool bottom_to_top = (screen.userdata & BottomToTop);
107: int pos = (int)screen.userdata;
1.1 root 108:
109: // 0 1 2 3 4 5 6
110: // 0123456789012345678901234567890123456789012345678901234567890123456789
111: // No. FromAddr Instruction ToAddr Iteration
112: // 001 U:01234567(01234567:bcnd.n) -> $01234567 x123456789
113: // 001 U:01234567(0123:trap ) -> $01234567 x123456789
114: // 002 S:01234567< 2> Bus Error
115: // 002 S:01234567< 69> MFP Timer-C
116: // 01234567890123456789012
117:
1.1.1.4 root 118: screen.Clear();
119: row = screen.GetRow();
1.1 root 120:
121: // 最初の1行は常にヘッダ
1.1.1.7 root 122: std::string header = FormatHeader();
123: screen.Puts(0, 0, header.c_str());
1.1 root 124:
125: // pos は最新を 0 とした通し番号。(スクロールしてたら開始が 0 とは限らない)
126: // t が entry[] 上の現在位置。
127: // y は表示座標。(上から表示か下から表示かが変わる)
128: t = top - pos;
129: int pos_end = pos + row - 1;
130: if (bottom_to_top == false) {
131: y = 1;
132: ydelta = 1;
133: } else {
134: y = row - 1;
135: ydelta = -1;
136: }
137: for (; pos < pos_end; pos++, y += ydelta) {
138: BranchEntry& e = entry[t--];
139: if (e.count == 0) {
140: continue;
141: }
142: std::string str = FormatEntry(e);
1.1.1.9 root 143: screen.Print(0, y, "%3u %s", pos, str.c_str());
1.1 root 144: if (e.count > 1) {
1.1.1.7 root 145: screen.Print(x_count, y, "x%9u", e.count);
1.1 root 146: }
147: }
148: }
149:
1.1.1.7 root 150: // ヘッダ文字列を返す。(m68k, m88k 共通)
151: std::string
152: BranchHistory::FormatHeader() const
153: {
154: if (is_exhist) {
155: // 012345678901234567890123456789012345678901234567890123456789
156: return "No. FromAddr Vec. Exception Iteration";
157: } else {
158: // 012345678901234567890123456789012345678901234567890123456789
159: return "No. FromAddr Instruction ToAddr Iteration";
160: }
161: }
1.1 root 162:
163:
164: //
1.1.1.11 root 165: // m680x0 ブランチ履歴
1.1 root 166: //
167:
1.1.1.8 root 168: // m680x0 ブランチ履歴では、通常ブランチ、例外発生 (IOCS コールと
169: // それ以外)、例外ベクタによるジャンプの4つを区別する。
1.1 root 170: //
1.1.1.8 root 171: // 1. 通常の分岐:
172: // from は上位31ビットが分岐元 PC、最下位ビットが S/U。
173: // to は32ビット全体が分岐先 PC。
1.1.1.11 root 174: // info は上位2ビットが %00 (Normal)、下位16ビットに分岐元命令の1ワード目。
1.1.1.8 root 175: //
1.1.1.11 root 176: // 3 2 1 0
177: // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
178: // +-+-+-----------+---------------+---------------+---------------+
179: // |0 0| 0 | Instruction Word |
180: // +-+-+-----------+---------------+---------------+---------------+
1.1.1.8 root 181: //
1.1.1.11 root 182: // 2. 例外発生 (IOCS/DOS コール以外の場合):
1.1.1.8 root 183: // from は上位31ビットが例外発生時の PC、最下位ビットが S/U。
184: // to は不問だが 0 にすること。
1.1.1.11 root 185: // info は上位2ビットが %11 (Exception)、最下位バイトがベクタ番号 (0-255)。
186: // 例外が TRAP#15 だが IOCS コールっぽくない場合と、
187: // 例外が F ライン例外だが DOS コールっぽくない場合もこちら。
188: //
189: // +-+-+-----------+---------------+---------------+---------------+
190: // |1 1| 0 | 0 | Vector No. |
191: // +-+-+-----------+---------------+---------------+---------------+
1.1.1.8 root 192: //
1.1.1.13! root 193: // 3. 例外発生 (各種サービスコールの場合):
1.1.1.8 root 194: // from, to は同じ。
1.1.1.11 root 195: // info は上位2ビットが %10 (ServiceCall)。
1.1.1.13! root 196: // info の上位ワードが OS 種別。
! 197: // $0 なら Human68k
! 198: // $1 なら NetBSD/m68k
! 199: // Human68k なら下位ワードは
! 200: // IOCS コールなら、b8 が %0 で b7-b0 に IOCS コール番号。
! 201: // DOS コールなら、b8 が %1 で b7-b0 に DOS コール番号。
! 202: // NetBSD/m68k なら下位ワードはシステムコール番号。
! 203: //
! 204: // +-+-+-----------+-------------+-+---------------+---------------+
! 205: // |1 0| $0| 0 |0| IOCS Call No. |
! 206: // +-+-+-----------+-------------+-+---------------+---------------+
! 207: // +-+-+-----------+-------------+-+---------------+---------------+
! 208: // |1 0| $0| - |1| DOS Call No. |
! 209: // +-+-+-----------+-------------+-+---------------+---------------+
! 210: // +-+-+-----------+-------------+-+---------------+---------------+
! 211: // |1 0| $1| System Call No. |
! 212: // +-+-+-----------+-------------+-+---------------+---------------+
1.1 root 213: //
1.1.1.8 root 214: // 4. 例外ベクタによる分岐 (例外処理の最後に起きる):
1.1.1.3 root 215: // from はベクタアドレス、
216: // to は分岐先 (0 も起こりえる)、
1.1.1.11 root 217: // info は上位2ビットが %01 (VectorJump)。
1.1.1.8 root 218: //
1.1.1.11 root 219: // +-+-+-----------+---------------+---------------+---------------+
220: // |0 1| 0 |
221: // +-+-+-----------+---------------+---------------+---------------+
1.1 root 222:
223: // コンストラクタ
1.1.1.9 root 224: BranchHistory_m680x0::BranchHistory_m680x0(uint objid_)
1.1.1.7 root 225: : inherited(objid_)
1.1 root 226: {
227: }
228:
229: // デストラクタ
230: BranchHistory_m680x0::~BranchHistory_m680x0()
231: {
232: }
233:
234: // 1エントリ分の表示内容作成
235: std::string
236: BranchHistory_m680x0::FormatEntry(const BranchEntry& e)
237: {
238: // m68k では from の最下位1ビットを S/U として使う
1.1.1.13! root 239: std::string desc = string_format("%c.%08x",
1.1 root 240: (e.from & 1) ? 'S' : 'U', (e.from & ~1));
241:
1.1.1.11 root 242: switch (e.info >> 30) {
1.1.1.8 root 243: case 0: // 通常分岐
244: {
1.1 root 245: const char *mnemonic;
1.1.1.11 root 246: uint32 inst = e.info & 0xffff;
1.1 root 247: if (inst == 0x4e73) {
248: mnemonic = ":rte";
249: } else if (inst == 0x4e75) {
250: mnemonic = ":rts";
251: } else if (inst == 0x4e77) {
252: mnemonic = ":rtr";
253: } else if ((inst & 0xffc0) == 0x4e80) {
254: mnemonic = ":jsr";
255: } else if ((inst & 0xffc0) == 0x4ec0) {
256: mnemonic = ":jmp";
257: } else if ((inst & 0xfff8) == 0x51c8) {
258: mnemonic = ":dbra";
259: } else if ((inst & 0xf0f8) == 0x50c8) {
260: mnemonic = ":dbcc";
261: } else if ((inst & 0xff00) == 0x6000) {
262: mnemonic = ":bra";
263: } else if ((inst & 0xff00) == 0x6100) {
264: mnemonic = ":bsr";
265: } else if ((inst & 0xf000) == 0x6000) {
266: mnemonic = ":bcc";
267: } else if ((inst & 0xfff8) == 0xf248) {
268: mnemonic = ":fdbcc";
269: } else if ((inst & 0xff80) == 0xf280) {
270: mnemonic = ":fbcc";
271: } else {
272: mnemonic = "";
273: }
274: desc += string_format("(%04x%-6s) -> $%08x", inst, mnemonic, e.to);
1.1.1.8 root 275: break;
276: }
277:
278: case 1: // 例外ベクタフェッチによるジャンプ
279: // 例外ベクタフェッチしてジャンプ
280: desc += string_format("(vector fetch) -> $%08x", e.to);
281: break;
282:
1.1.1.11 root 283: case 2: // 例外発生 (IOCS/DOS コール)
1.1.1.8 root 284: {
1.1.1.11 root 285: uint vector;
1.1.1.13! root 286: if (((e.info >> 16) & 0x01) == 0) {
! 287: // Human68k、IOCS/DOS コール
! 288: const char *call;
! 289: const char *name;
! 290: uint num = e.info & 0xff;
! 291: if ((e.info & 0x0100) == 0) {
! 292: call = "IOCS";
! 293: name = m680x0disasm::GetIOCSName(num);
! 294: vector = M68K::EXCEP_TRAP15;
! 295: } else {
! 296: call = "DOS";
! 297: name = m680x0disasm::GetDOSName(num);
! 298: vector = M68K::EXCEP_FLINE;
! 299: }
! 300: if (name != NULL) {
! 301: desc += string_format("<%3u> %s %s", vector, call, name);
! 302: } else {
! 303: desc += string_format("<%3u> %s $%02x", vector, call, num);
! 304: }
1.1.1.8 root 305: } else {
1.1.1.13! root 306: // NetBSD/m68k システムコール
! 307: vector = M68K::EXCEP_TRAP0;
! 308: const char *name = vectortable->GetExceptionName(vector);
! 309: uint num = e.info & 0xffff;
! 310: desc += string_format("<%3u> %s (syscall %u)", vector, name, num);
1.1.1.8 root 311: }
312: break;
313: }
314:
1.1.1.11 root 315: case 3: // 例外発生 (IOCS/DOS コール以外)
1.1.1.8 root 316: {
1.1.1.11 root 317: uint32 vector = e.info & 0xff;
1.1.1.8 root 318: const char *name = vectortable->GetExceptionName(vector);
1.1.1.9 root 319: desc += string_format("<%3u> %s", vector, name ?: "");
1.1.1.8 root 320: break;
321: }
322:
323: default:
324: __unreachable();
1.1 root 325: }
326: return desc;
327: }
328:
329:
330: //
1.1.1.11 root 331: // m88xx0 ブランチ履歴
1.1 root 332: //
333:
334: // m88xx0 ブランチ履歴では、通常ブランチ、例外発生の2つを区別する。
335: // m68k は例外ベクタに書いてあるアドレスに飛ぶのでもう1種類分けてあったが、
336: // m88k は例外ベクタを直接実行するのでそれは不要。
337: //
338: // 通常の分岐:
1.1.1.3 root 339: // from は上位30ビットが分岐元 XIP、最下位ビットが S/U、
340: // to は32ビット全体が分岐先アドレス、
1.1.1.11 root 341: // info は命令ワード。
1.1 root 342: //
343: // 例外発生:
1.1.1.3 root 344: // from は上位30ビットが例外発生時の XIP、最下位ビットが S/U、
1.1.1.12 root 345: // to は不問。
1.1.1.11 root 346: // info は、9 ビットのベクタ番号と、ベクタが OpenBSD/m88k システムコール
347: // の場合はシステムコール番号を含む。
348: // 3 2 1 0
349: // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
350: // +---------------+---------------+-------+-----+-+---------------+
351: // |1 1 0 0 0 0 0 0| System Call No. |0 0 0| Vector Number |
352: // +---------------+---------------+-------+-----+-+---------------+
1.1.1.3 root 353: // 命令ワードとは衝突しない (instruction.txt 参照)。
1.1 root 354:
355: // コンストラクタ
1.1.1.9 root 356: BranchHistory_m88xx0::BranchHistory_m88xx0(uint objid_)
1.1.1.7 root 357: : inherited(objid_)
1.1 root 358: {
359: }
360:
361: // デストラクタ
362: BranchHistory_m88xx0::~BranchHistory_m88xx0()
363: {
364: }
365:
366: // 1エントリ分の表示内容作成
367: std::string
368: BranchHistory_m88xx0::FormatEntry(const BranchEntry& e)
369: {
370: // m88k では from の最下位1ビットを S/U として使う
1.1.1.13! root 371: std::string desc = string_format("%c.%08x",
1.1 root 372: (e.from & 1) ? 'S' : 'U', (e.from & ~1));
373:
1.1.1.12 root 374: if (e.info >= 0xfc000000) {
1.1 root 375: // 例外発生記録
376: // 発生アドレスとベクタを表示
1.1.1.11 root 377: uint32 vector = e.info & 0x1ff;
1.1.1.7 root 378: const char *name = vectortable->GetExceptionName(vector);
1.1.1.9 root 379: desc += string_format("<%3u> %s", vector, name ?: "");
1.1.1.7 root 380: if (vector == 128 || vector == 450) {
381: // 450 なら OpenBSD のシステムコール番号を追加。
382: // 非公式 NetBSD/luna88k は(今の所?) 128 を使ってるようだ。
1.1.1.11 root 383: desc += string_format("(%u)", (e.info >> 12) & 0xfff);
1.1 root 384: }
1.1.1.11 root 385: } else if (e.info >= 0xc0000000) {
1.1 root 386: // ブランチ
387: const char *mnemonic = "";
1.1.1.11 root 388: uint32 up4 = (e.info >> 26) & 0xf;
1.1 root 389: switch (up4) {
390: case 0: mnemonic = ":br"; break;
391: case 1: mnemonic = ":br.n"; break;
392: case 2: mnemonic = ":bsr"; break;
393: case 3: mnemonic = ":bsr.n"; break;
394: case 4: mnemonic = ":bb0"; break;
395: case 5: mnemonic = ":bb0.n"; break;
1.1.1.2 root 396: case 6: mnemonic = ":bb1"; break;
1.1 root 397: case 7: mnemonic = ":bb1.n"; break;
398:
399: case 0xa: mnemonic = ":bcnd"; break;
400: case 0xb: mnemonic = ":bcnd.n"; break;
401: case 0xc: {
1.1.1.11 root 402: uint32 lo6 = (e.info >> 10) & 0x3f;
1.1 root 403: if (lo6 == 0x34) {
404: mnemonic = ":tb0";
405: } else if (lo6 == 0x36) {
406: mnemonic = ":tb1";
407: } else if (lo6 == 0x3a) {
408: mnemonic = ":tcnd";
409: }
410: break;
411: }
412: case 0xd: {
1.1.1.11 root 413: uint32 lo6 = (e.info >> 10) & 0x3f;
1.1 root 414: if (lo6 == 0x30) {
1.1.1.11 root 415: if ((e.info & 0x1f) == 1) {
1.1 root 416: mnemonic = ":jmp r1";
417: } else {
418: mnemonic = ":jmp";
419: }
420: } else if (lo6 == 0x31) {
421: mnemonic = ":jmp.n";
422: } else if (lo6 == 0x32) {
423: mnemonic = ":jsr";
424: } else if (lo6 == 0x33) {
425: mnemonic = ":jsr.n";
426: } else if (lo6 == 0x3f) {
427: mnemonic = ":rte";
428: }
429: break;
430: }
431: case 0xe: mnemonic = ":tbnd"; break;
432: default:
433: break;
434: }
1.1.1.11 root 435: desc += string_format("(%08x%-7s) -> $%08x", e.info, mnemonic, e.to);
1.1 root 436: }
437: return desc;
438: }
1.1.1.7 root 439:
440:
441: //
442: // XP ブランチ履歴
443: //
444:
445: // XP ブランチ履歴では、通常ブランチ、例外発生、例外ベクタによるジャンプの
446: // 3つを区別する。
447: //
448: // 通常の分岐
449: // from は下位 16 ビットが分岐元 PC
450: // to は下位 16 ビットが分岐先 PC
1.1.1.11 root 451: // info の最上位ビットが %0 で区別する。
452: // info の下位 16 ビットに命令を上詰めで。例えば
1.1.1.7 root 453: // CALL (1バイト命令 CD nn nn) なら $0000'cd00、
454: // RETI (2バイト命令 ED 4D) なら $0000'ed4d。
455: //
456: // 例外発生
457: // from は下位 16 ビットが例外発生時の PC
458: // to は $ffffffff ならこのエントリはベクタ方式の例外発生時側を示す、
459: // そうでなければダイレクト方式で下位 16 ビットが分岐先 PC
1.1.1.11 root 460: // info の最上位ビットが %1 で info が $ffffffff でなければ
1.1.1.7 root 461: // 下位に優先度(0-15)。
462: //
463: // 例外ベクタジャンプの場合
464: // from はベクタアドレス
465: // to は分岐先 PC、
1.1.1.11 root 466: // info が $ffffffff で区別する。
1.1.1.7 root 467:
468: // コンストラクタ
1.1.1.9 root 469: BranchHistory_hd64180::BranchHistory_hd64180(uint objid_)
1.1.1.7 root 470: : inherited(objid_)
471: {
1.1.1.10 root 472: monitor->SetSize(48, 1 + 32); // ヘッダ1行とエントリ数
1.1.1.7 root 473: x_count = 38;
474: }
475:
476: // デストラクタ
477: BranchHistory_hd64180::~BranchHistory_hd64180()
478: {
479: }
480:
481: // ヘッダ文字列を返す。
482: std::string
483: BranchHistory_hd64180::FormatHeader() const
484: {
485: if (is_exhist) {
486: // 01234567890123456789012345678901234567890123456789
487: return "No. From Exception Iteration";
488: } else {
489: // 01234567890123456789012345678901234567890123456789
490: return "No. From Instruction ToAddr Iteration";
491: }
492: }
493:
494: // 1エントリ分の表示内容作成
495: std::string
496: BranchHistory_hd64180::FormatEntry(const BranchEntry& e)
497: {
498: // 0 1 2 3 4 5
499: // 012345678901234567890123456789012345678901234567890
500: // No. From Instruction ToAddr Iteration
501: // 001 0123(0000:CALL NZ) -> $0234 x123456899
502: // No. From Exception
503: // 002 0123<00> Timer Overflow
504: // 003 0038(vector fetch) -> $0000
505:
506: std::string desc = strhex(e.from, 4);
507:
1.1.1.11 root 508: if (e.info == 0xffffffff) {
1.1.1.7 root 509: // 例外ベクタフェッチしてジャンプ
510: desc += string_format("(vector fetch) -> $%04x", e.to);
1.1.1.11 root 511: } else if ((int32)e.info < 0) {
1.1.1.7 root 512: // 例外発生
1.1.1.11 root 513: uint32 vector = e.info & 0xff;
1.1.1.9 root 514: desc += string_format("<%2u>%-15s",
1.1.1.7 root 515: vector, MPU64180Device::InterruptName[vector]);
516: // 例外履歴には宛先は出さないほうに統一。
517: // ブランチ履歴でも、ベクタ方式なら宛先不要。
518: if (is_exhist == false && e.to != -1) {
519: desc += string_format(" -> $%04x", e.to);
520: }
521: } else {
522: // ブランチ
523: std::string mnemonic;
1.1.1.11 root 524: uint32 inst = e.info & 0xffff;
1.1.1.7 root 525: if (inst == 0x1000) {
526: mnemonic = ":DJNZ";
527: } else if (inst == 0x1800) {
528: mnemonic = ":JR";
529: } else if (inst == 0xc300) {
530: mnemonic = ":JP";
531: } else if (inst == 0xc900) {
532: mnemonic = ":RET";
533: } else if (inst == 0xcd00) {
534: mnemonic = ":CALL";
535: } else if (inst == 0xe900) {
536: mnemonic = ":JP (HL)";
537: } else if (inst == 0xed45) {
538: mnemonic = ":RETN";
539: } else if (inst == 0xed4d) {
540: mnemonic = ":RETI";
541: } else if (inst == 0xdde9) {
542: mnemonic = ":JP (IX)";
543: } else if (inst == 0xfde9) {
544: mnemonic = ":JP (IY)";
545: } else if ((inst & 0xe700) == 0x2000) {
546: mnemonic = ":JR " + fstr((inst >> 11) & 3);
547: } else if ((inst & 0xc700) == 0xc000) {
548: mnemonic = ":RET " + fstr((inst >> 11) & 7);
549: } else if ((inst & 0xc700) == 0xc200) {
550: mnemonic = ":JP " + fstr((inst >> 11) & 7);
551: } else if ((inst & 0xc700) == 0xc400) {
552: mnemonic = ":CALL " + fstr((inst >> 11) & 7);
553: } else if ((inst & 0xc700) == 0xc700) {
554: mnemonic = string_format(":RST %02xH", (inst >> 8) & 0x38);
555: }
556: desc += string_format("(%04x%-8s) -> $%04x",
557: inst, mnemonic.c_str(), e.to);
558: }
559: return desc;
560: }
561:
562: /*static*/ std::string
563: BranchHistory_hd64180::fstr(int f)
564: {
565: static const char fff[] =
566: "NZ\0\0"
567: "Z\0\0\0"
568: "NC\0\0"
569: "C\0\0\0"
570: "PO\0\0"
571: "PE\0\0"
572: "P\0\0\0"
573: "M";
574:
575: return &fff[f * 4];
576: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.