PageSourceSearch

https://retroapplejs.github.io/res/EMU_CARD_ramcard.js

js retroapplejs.github.io collected 2026-10-03 09:51:01 UTC 26,384 bytes, 601 lines download raw bytes

1//    ██████   █████  ███    ███  ██████  █████  ██████  ██████  
2//    ██   ██ ██   ██ ████  ████ ██      ██   ██ ██   ██ ██   ██ 
3//    ██████  ███████ ██ ████ ██ ██      ███████ ██████  ██   ██ 
4//    ██   ██ ██   ██ ██  ██  ██ ██      ██   ██ ██   ██ ██   ██ 
5//    ██   ██ ██   ██ ██      ██  ██████ ██   ██ ██   ██ ██████ 
6//
7// Copyright (c) 2026 Freddy Vandriessche.
8// notice: https://raw.githubusercontent.com/RetroAppleJS/RetroAppleJS.github.io/main/LICENSE.md
9//
10// EMU_CARD_ramcard.js
11
12// Discovery container. Apple2IO mounts a separate live instance.
13if(oEMU===undefined) var oEMU = {"component":{"IO":{"RamCard":new RamCard()}}}
14else oEMU.component.IO.RamCard = new RamCard();
15
16function RamCard()
17{
18    this.id     = {"PCODE":"MS16K", "icon":"fa fa-microchip"}
19    this.state  = {
20         "active":true          // flag to remember if peripheral is altogether operational or not 
21        ,"softswitch_pos":0x1   // default soft switch state (documented by microsoft)
22        ,"bMapped":false        // flag to remember if onboard ROM is mapped by the language card (true) or not (false)
23        ,"RR":1                 // previous qualifying access / double-trigger latch
24    };
25    //this.action = {"SlotIO": { "RD":{ "callback": function(addr) { return card.soft_switch(addr); } } } };  // generic callback for softswitches
26
27    this.action =
28    {
29        "SlotIO":
30        {
31            "RD": { "callback": function(addr,ctx)       { return card.soft_switch(addr,ctx); } },
32            "WR": { "callback": function(addr,d8,ctx)    { return card.soft_switch(addr,ctx); } }
33        }
34    };  // generic callback for language-card softswitches; reads and writes both trigger them
35
36    var bDebug_sw  = false;      // debug soft switch updates (light)
37    var bDebug_mon = false;     // debug RAM Write monitor
38    var bDebug     = false;     // debug all RAM R/W operations   
39
40    const ROM_ID        = _CFG_SYSCODE["A2P"].ROM;
41    const ROM_CFG       = _CFG_ROMRANGES[ROM_ID];
42    const ROM_RANGE     = oCOM.parseRngExpr(ROM_CFG.ROM);
43
44    const BANK_SIZE     = 0x1000;                     // 4K bank memory size = contiguous bank offset  = 4Kbytes  
45    const CONT_SIZE     = 0x2000;                     // 8K contiguous memory size
46    const BANK_TOT_SIZE = BANK_SIZE * 2;              // 2*4K linearised bank size
47    const TOTAL_SIZE    = BANK_TOT_SIZE + CONT_SIZE;  // 16K
48
49    var RAMCARD_MEM = new Uint8Array(TOTAL_SIZE);     // PERIPHERAL RAM
50
51    var hw;                      // purposed for late-binding (at restart)
52    var io;                      // purposed for late-binding (at restart)
53    var card       = this;       // stand-in in areas where 'this' is absent e.g. inside mapping functions
54
55    // RE: 0="READ-ENABLE" 1="READ-ENABLE" | WE: 0:"WRITE-PROTECT" 1:"WRITE-ENABLE"  | BANK: 0="BANK A", 1="BANK B"
56    var softswitch = {
57        0x0: {"RE":1,         "BANK":0}  // READ: $D000-$DFFF -> BANK_MEM[1]  | READ: $E000-$EFFF & $F000-$FFFF -> RAMCARD_MEM
58       ,0x1: {        "WE":1, "BANK":0}  // READ: $D000-$DFFF & $E000-$EFFF & $F000-$FFFF -> apple2Rom | WRITE $D000-$DFFF -> BANK_MEM[1]  | WRITE: $E000-$EFFF & $F000-$FFFF -> RAMCARD_MEM
59       ,0x2: {                "BANK":0}  // READ: $D000-$DFFF & $E000-$EFFF & $F000-$FFFF -> apple2Rom  
60       ,0x3: {"RE":1, "WE":1, "BANK":0}  // READ: $D000-$DFFF -> BANK_MEM[1]  | READ: $E000-$EFFF & $F000-$FFFF -> RAMCARD_MEM | WRITE $D000-$DFFF -> BANK_MEM[1]  | WRITE: $E000-$EFFF & $F000-$FFFF -> RAMCARD_MEM
61
62       ,0x8: {"RE":1,         "BANK":1}  // READ: $D000-$DFFF -> BANK_MEM[0]  | READ: $E000-$EFFF & $F000-$FFFF -> RAMCARD_MEM
63       ,0x9: {        "WE":1, "BANK":1}  // READ: $D000-$DFFF & $E000-$EFFF & $F000-$FFFF -> apple2Rom | WRITE $D000-$DFFF -> BANK_MEM[0]  | WRITE: $E000-$EFFF & $F000-$FFFF -> RAMCARD_MEM
64       ,0xA: {                "BANK":1}  // READ: $D000-$DFFF & $E000-$EFFF & $F000-$FFFF -> apple2Rom  
65       ,0xB: {"RE":1, "WE":1, "BANK":1}  // READ: $D000-$DFFF ->
65 BANK_MEM[0]  | READ: $E000-$EFFF & $F000-$FFFF -> RAMCARD_MEM | WRITE $D000-$DFFF -> BANK_MEM[0]  | WRITE: $E000-$EFFF & $F000-$FFFF -> RAMCARD_MEM
66    }
67    var softswitch_diff = {RE:0,WE:0,BANK:0};
68
69    this.mapRead = function(addr)
70    {
71        return card.read(abs2rel(addr));
72    };
73
74    this.mapWrite = function(addr,d8)
75    {
76        var rel_addr = abs2rel(addr);
77        var sw = softswitch[card.state.softswitch_pos] || {};
78
79        card.write(rel_addr,d8);
80        card.mark_MEM_monitoring(
81            rel_addr<BANK_SIZE ? (sw.BANK==0 ? "bankA" : "bankB") : "cont",
82            rel_addr
83        );
84    };
85
86    this.updateMemoryMap = function(bRamcardActive)
87    {
88        if(hw===undefined) { hw = apple2plus.hwObj(); console.warn("EMU_CARD_ramcard.js lost hw from scope") }
89        if(io===undefined) { io = apple2plus.hwObj().io; console.warn("EMU_CARD_ramcard.js lost io from scope") }
90
91        if(this.bMEM_monitoring != hw.bMEM_monitoring)
92            this.enable_MEM_monitoring(hw.bMEM_monitoring);  // follow mem monitoring flag from hardware
93        if(bDebug_sw)
94
95            debug_updateMemoryMap(bRamcardActive,this);      // log memory map updates
96        if(!io || !io.MEMORY_MAP || !this.mount) return false;
97
98        var sw = softswitch[this.state.softswitch_pos] || {};
99        var ruleID = this.id.PCODE+":"+this.mount.hash;
100        var ok = io.MEMORY_MAP.runRule(ruleID,{
101             "position":"$"+oCOM.getHexByte(this.state.softswitch_pos)
102            ,"RE":sw.RE==1
103            ,"WE":sw.WE==1
104            ,"RR":this.state.RR==1
105            ,"BANK":sw.BANK==0 ? "A" : "B"
106            ,"mapped":!!bRamcardActive
107        });
108
109        if(ok) this.state.bMapped = !!bRamcardActive;
110        return ok;
111    };
112
113    this.reset = function()
114    {
115        // Microsoft RAMCard RESET state is equivalent to two accesses of $C081:
116        // BANK A internally, ROM readable, LC RAM writable.
117        this.state.softswitch_pos = 0x1;
118        this.state.bMapped        = false;
119        this.state.RR             = 1;
120        if(io && this.mount) this.updateMemoryMap(false);
121    };
122
123    this.restart = function()
124    {
125        hw = apple2plus.hwObj();
126        io = apple2plus.hwObj().io;
127        debug_flush();
128        this.state.softswitch_pos = 0x1;
129        this.state.bMapped        = false;
130        this.state.RR             = 1;
131
132        var ruleID = this.id.PCODE+":"+this.mount.hash;
133        io.MEMORY_MAP.addRule(ruleID,function(state)
134        {
135            var writeEnabled = state.WE && state.RR;
136            return {
137                 "owner":card
138                ,"source":"MS16K language-card soft switches"
139                ,"state":state
140                ,"mappings":[
141                    {
142                         "id":"upper-memory-read"
143                        ,"space":ROM_ID
144                        ,"op":"RD"
145                        ,"range":ROM_CFG.ROM
146                        ,"handler":state.mapped ? "mapRead" : "@default"
147                        ,"target":state.mapped ? "MS16K RAM" : "Apple II ROM"
148                        ,"enabled":true
149                        ,"condition":"RE="+Number(state.RE)+"; mapped="+Number(state.mapped)+"; bank="+state.BANK
150                    },
151                    {
152                         "id":"upper-memory-write"
153                        ,"space":ROM_ID
154                        ,"op":"WR"
155                        ,"range":ROM_CFG.ROM
156                        ,"handler":"mapWrite"
157                        ,"target":"MS16K RAM - bank "+state.BANK+" and contiguous 8K"
158                        ,"enabled":writeEnabled
159                        ,"condition":"WE="+Number(state.WE)+"; RR="+Number(state.RR)+"; bank="+state.BANK
160                    }
161                ]
162            };
163        });
164
165        // Register the initial ROM-read/RAM-write routing and its evidence.
166        this.updateMemoryMap(false);
167
168
169        var ramcard = apple2plus.hwObj().io.HASH2obj( this.mount.hash  ); // reference the ramcard object by hash
170        oCOM.addRefreshEvent(ramcard.MEM_monitoring,"MEM_monitoring_MS16K",false);  // enabled together with the main memory monitor by the UI button
171    }
172
173    function abs2rel(addr)     { return ((addr & 0xFFFF) - ROM_RANGE.from); }
174    function rel2abs(rel_addr) { return (rel_addr + ROM_RANGE.from) & 0xFFFF; }
175
176    // translate addresses from the bus to ramcard addresses 
177    function ramcard_address_encoder(addr,bank_bit) 
178    { 
179        return addr + ((bank_bit | !!(addr >> 12)) << 12);
180    }
181
182    this.soft_switch = function(rel_io_addr)
183    {
184        if(io===undefined) { io = apple2plus.hwObj().io;
184  console.warn("EMU_CARD_ramcard.js lost io from scope") }
185
186        const slot = 0;                                                         // TODO: take fromthis.state.slot
187        const pre_sw = softswitch[sw_mask(this.state.softswitch_pos)];          // previous switch state
188        const status_nibble = (pre_sw.BANK==1 ? 1 : 0) | (pre_sw.RE==1 ? 2 : 0) | (pre_sw.WE==1 ? 4 : 0) | (this.state.RR==1 ? 8 : 0);
189        if(apple2plus.hwObj().bRO == true) return status_nibble;                // skip soft switch manupulation; just return status when hardware is in Read-Only mode
190
191        this.state.softswitch_pos = sw_mask(io.address_encoder(rel_io_addr,"SlotIO",slot)); // set softswitch position
192        const sw = softswitch[this.state.softswitch_pos] || {}; mon_soft_switch(sw);        // access soft switch
193        debug_flush(); if(bDebug_sw) debug_soft_switch(sw,this.state);
194       
195        /*
196         * RR represents whether the PREVIOUS LC control access qualified for
197         * write enable. updateMemoryMap() must therefore see RR before this
198         * access changes it:
199         *
200         *   first odd access:  WE=1, RR=0 -> writes remain protected
201         *   second odd access: WE=1, RR=1 -> writes become enabled
202         *
203         * An even/write-protect access clears RR for the following access.
204         * Read mapping (RE) is independent and takes effect immediately.
205         */
206        var ok = this.updateMemoryMap(!!sw.RE);
207        this.state.RR = sw.WE==1 ? 1 : 0;
208
209        if(bDebug_sw && !ok) console.warn(this.id.PCODE+": SOFTSWITCH could not update memory map");
210
211
212        // MEM_monitoring() updates the soft-switch display only while monitoring is enabled.
213        return status_nibble;
214    };
215
216    // mask io address (Ramcard specifically ignores bit 2, e.g. $C085 -> hits soft switch 0x1, as 0x5 is not a distinct soft switch)
217    function sw_mask(sw) { return sw & 0b1011; }
218
219
220    this.read = function(rel_addr)
221    {
222        var sw = softswitch[this.state.softswitch_pos] || {};
223        const d8 = RAMCARD_MEM[ ramcard_address_encoder(rel_addr,sw.BANK) ];
224        if(apple2plus.hwObj().bRO != true)
225        {
226            mon_soft_switch(sw);
227            if(bDebug) debug_record( rel_addr < BANK_SIZE ? DBG_READ_BANK : DBG_READ_RAMCARD, rel_addr, d8, rel_addr < BANK_SIZE ? (sw.BANK==0 ? "A" : "B") : null );
228        }
229        return d8;
230    };
231
232    this.write = function(rel_addr,d8)
233    {
234        var sw = softswitch[this.state.softswitch_pos] || {}; mon_soft_switch(sw);
235        if(sw.WE==1 && this.state.RR==1)
236        {
237            RAMCARD_MEM[ ramcard_address_encoder(rel_addr,sw.BANK) ] = d8;
238            if(bDebug) debug_record( rel_addr < BANK_SIZE ? DBG_WRITE_BANK : DBG_WRITE_RAMCARD, rel_addr, d8, rel_addr < BANK_SIZE ? (sw.BANK==0 ? "A" : "B") : null );
239        }
240        else
241        {
242            debug_flush();
243            console.warn(this.id.PCODE+": FAILED ATTEMPT: (WE="+(sw.WE==1?true:false)+") BANK"+(sw.BANK==0?"A":"B")+" write #$"+oCOM.getHexByte(d8)+" at addr $"+oCOM.getHexWord(rel2abs(rel_addr)));
244        }
245    };
246
247
248    //       ______  _____  _____  _____    __             __                                 
249    //     .' ___  ||_   _||_   _||_   _|  [  |           [  |                                
250    //    / .'   \_|  | |    | |    | |     | |--.  .---.  | | _ .--.   .---.  _ .--.  .--.   
251    //    | |   ____  | '    ' |    | |     | .-. |/ /__\\ | |[ '/'`\ \/ /__\\[ `/'`\]( (`\]  
252    //    \ `.___]  |  \ \__/ /    _| |_    | | | || \__., | | | \__/ || \__., | |     `'.'.  
253    //     `._____.'    `.__.'    |_____|  [___]|__]'.__.'[___]| ;.__/  '.__.'[___]   [\__) ) 
254    //                                                        [__|                            
255
256    this.MEM_status_id = this.id.PCODE+"_softswitch_status";
257    this.mem_mon = {"cont":{}, "bankA":{}, "bankB":{}};
258    this.bMEM_monitoring = false;
259    this.MEM_grid_cnf = {"id_prefix":"x","digits":5};
260    this.mem_layout = 
261    {
262        "3F000-40000":["#D06060",this.id.PCODE+" CONT"  ,"RF"]
263       ,"3E000-3F000":["#D06060",this.id.PCODE+" CONT"  ,"RE"]
264       ,"2D000-2E000":["#B05050",this.id.PCODE+" BANK B","DB"]
265       ,"1D000-1E000":["#A04040",this.id.PCODE+" BANK A","DA"]
266    };
267    this.MEM_grid = 
268    {
269        "cont":   {"cnf":{"id_prefix":"x","digits":4},"layout":{ "F000-FFFF":["#D06060",this.id.PCODE+ "CONT"  ,"RF"]
270                                                                ,"E000-FFFF":["#D06060",this.id.PCODE+" CONT"  ,"RE"] }},
271        "bankB":  {"cnf":{"id_prefix":"z","digits":4},"layout":{ "D000-DFFF":["#B05050",this.id.PCODE+" BANK B","DB"] }},
272        "bankA":  {"cnf":{"id_prefix":"y","digits":4},"layout":{ "D000-DFFF":["#A04040",this.id.PCODE+" BANK A","DA"] }}
273    };
274    var tm = Number(new Date());
275    this.MEM_status_text = function()
276    {
277        var on_icon = (softswitch_diff.RE>0)   ? '<i class="fa fa-microchip" title="'+this.id.PCODE+' RAM"></i>'         : '<i class="fa fa-apple-alt" title="'+this.id.PCODE+' ROM"></i>';
278        var we_icon = (softswitch_diff.WE>0)   ? '<i class="fa fa-lock-open" title="'+this.id.PCODE+' WRITE ENABLE"></i>': '<i class="fa fa-lock" title="'+this.id.PCODE+' WRITE PROTECTED"></i>';
279        var bank    = (softswitch_diff.BANK>0) ? '<span title="'+this.id.PCODE+' BANK B">B</span>'                       : '<span title="'+this.id.PCODE+' BANK A">A</span>'; 
280
281        // debug
282        //on_icon += " "+softswitch_diff.RE;
283        //we_icon += " "+softswitch_diff.WE;
284        //bank    += " "+softswitch_diff.BANK;
285
286        softswitch_diff = {RE:0,WE:0,BANK:0};
287        return this.id.PCODE+' ' + on_icon + ' ' + we_icon + ' ' + bank;
288    };
289    this.update_MEM_status = function()
290    {
291        if(typeof(document)!="object") return false;
292        var el = document.getElementById(this.MEM_status_id); if(!el) return false;
293        el.innerHTML = this.MEM_status_text();
294        return true;
295    };
296    this.build_MEM_map = function(linestep)
297    {
298        linestep = linestep || 0x1000;
299        return "<div style='display:flex;flex-direction:column;gap:6px;align-items:flex-start'>"
300             +   oMEMGRID.build_grid(0xF000,2,-linestep,{id_prefix:"x",digits:4,table_id:"xEF"})
301             +   oMEMGRID.build_grid(0xD000,1,-linestep,{id_prefix:"z",digits:4,table_id:"zD"})
302             +   oMEMGRID.build_grid(0xD000,1,-linestep,{id_prefix:"y",digits:4,table_id:"yD"})
303             +   "<div id='"+this.MEM_status_id+"' style='float:right;margin-left:30px;padding:2px;height:20px;margin-top:-6px;background-color:white;border-radius:5px'>"
304             +     this.MEM_status_text()
305             +   "</div>"
306             + "</div>";
307    };
308    this.paint_MEM_map = function()
309    {
310        if(!this.MEM_grid) return false;
311
312        oMEMGRID.relabel_grid_rows("xEF",["F000    ","E000    "]);
313        oMEMGRID.relabel_grid_rows("zD",["D000-B"]);
314        oMEMGRID.relabel_grid_rows("yD",["D000-A"]);
315        oMEMGRID.paint_grid(this.MEM_grid.cont.layout, this.MEM_grid.cont.cnf);
316        oMEMGRID.paint_grid(this.MEM_grid.bankB.layout,   this.MEM_grid.bankB.cnf);
317        oMEMGRID.paint_grid(this.MEM_grid.bankA.layout,   this.MEM_grid.bankA.cnf);
318        this.update_MEM_status();
319
320        return true;
321    };
322
323    // mark one block in the memory grid
324    this.mark_MEM_monitoring = function(bucket, rel_addr)
325    {
326        if(!this.bMEM_monitoring) return;
327        if(!this.mem_mon[bucket]) this.mem_mon[bucket] = {};
328
329        this.mem_mon[bucket][rel2abs(rel_addr) >> oMEMGRID.mem_gran] = true;
330
331        if(hw && !hw.mem_mon_trigger[this.id.PCODE])
332        {
333            this.MEM_monitoring();
334            hw.mem_mon_trigger[this.id.PCODE] = true;
335        }
336    };
337
338    // clear all blocks in the memory grid
339    this.reset_MEM_monitoring = function()
340    {
341        if(bDebug_mon) console.log('MEM_monitoring_'+this.id.PCODE+' -> reset_MEM_monitoring()');
342        this.mem_mon = {"cont":{}, "bankA":{}, "bankB":{}};
343        if(this.MEM_grid)
344        {
345            oMEMGRID.paint_grid(this.MEM_grid.cont.layout, this.MEM_grid.cont.cnf);
346            oMEMGRID.paint_grid(this.MEM_grid.bankB.layout,this.MEM_grid.bankB.cnf);
347            oMEMGRID.paint_grid(this.MEM_grid.bankA.layout,this.MEM_grid.bankA.cnf);
348        }
349        this.update_MEM_status();
350    }
351
352    // enable/disable monitoring - actioned by a UI button
353    this.enable_MEM_monitoring = function(b)
354    {
355        if(bDebug_mon) console.log('MEM_monitoring_'+this.id.PCODE+' -> enable_MEM_monitoring('+b+')');
356        this.bMEM_monitoring = !!b;
357        if(b) this.reset_MEM_monitoring();  // clear data
358    }
359
360    // lights up memory blocks in the grid
361    // called regularly as the result of oCOM.addRefreshEvent  (e.g. 2 times per second)
362    this.MEM_monitoring = function()
363    {
364        if(!(this.state && this.MEM_grid)) return;
365        this.update_MEM_status();        
366        if(bDebug_mon) console.log(JSON.stringify( 'MEM_monitoring_'+this.id.PCODE+' ->
366 bankA:'+JSON.stringify(this.mem_mon.bankA)+' bankB:'+JSON.stringify(this.mem_mon.bankB)+' cont:'+JSON.stringify(this.mem_mon.cont)) );
367
368        if(Object.keys(this.mem_mon.cont).length>0)    // is there anything to display?
369        {
370            oMEMGRID.paint_grid(this.MEM_grid.cont.layout, this.MEM_grid.cont.cnf);
371            oMEMGRID.update_grid(this.mem_mon.cont, this.MEM_grid.cont.cnf);
372        }
373        if(Object.keys(this.mem_mon.bankB).length>0)    // is there anything to display?
374        {
375            oMEMGRID.paint_grid(this.MEM_grid.bankB.layout,   this.MEM_grid.bankB.cnf);
376            oMEMGRID.update_grid(this.mem_mon.bankB,   this.MEM_grid.bankB.cnf);
377        }
378
379        if(Object.keys(this.mem_mon.bankA).length>0)    // is there anything to display?
380        {
381            oMEMGRID.paint_grid(this.MEM_grid.bankA.layout,   this.MEM_grid.bankA.cnf);
382            oMEMGRID.update_grid(this.mem_mon.bankA,   this.MEM_grid.bankA.cnf);
383        }
384        
385        if(hw.bClear_mon) this.mem_mon = {"cont":{}, "bankA":{}, "bankB":{}};  // CLEAR THE GRID AFTER EACH DISPLAY
386    }
387
388    // Vote on ramcard status (since softswitches change more rapidly than visual updates, we want to show the most prevalent state) 
389    function mon_soft_switch(sw)
390    {
391        softswitch_diff.RE   += sw.RE==1 ? 1 : -1;
392        softswitch_diff.WE   += (sw.WE==1 && card.state.RR==1) ? 1 : -1;
393        softswitch_diff.BANK += sw.BANK==1 ? 1 : -1;
394    }
395
396
397    //      _____       ___      ______     __             __                                 
398    //     |_   _|    .'   `.  .' ___  |   [  |           [  |                                
399    //       | |     /  .-.  \/ .'   \_|    | |--.  .---.  | | _ .--.   .---.  _ .--.  .--.   
400    //       | |   _ | |   | || |   ____    | .-. |/ /__\\ | |[ '/'`\ \/ /__\\[ `/'`\]( (`\]  
401    //      _| |__/ |\  `-'  /\ `.___]  |   | | | || \__., | | | \__/ || \__., | |     `'.'.  
402    //     |________| `.___.'  `._____.'   [___]|__]'.__.'[___]| ;.__/  '.__.'[___]   [\__) ) 
403    //                                                        [__|                                                     
404
405    var dbgCurrent = null;
406    var logStep = 0;
407
408    var DBG_READ_BANK      = "READ RAM BANK ";
409    var DBG_READ_RAMCARD   = "READ RAM CONT ";
410    var DBG_WRITE_BANK     = "WRITE RAM BANK ";
411    var DBG_WRITE_RAMCARD  = "WRITE RAM CONT ";
412
413    var dbgOps = {};
414    dbgOps[DBG_READ_BANK]     = dbg_op(DBG_READ_BANK);
415    dbgOps[DBG_READ_RAMCARD]  = dbg_op(DBG_READ_RAMCARD);
416    dbgOps[DBG_WRITE_BANK]    = dbg_op(DBG_WRITE_BANK);
417    dbgOps[DBG_WRITE_RAMCARD] = dbg_op(DBG_WRITE_RAMCARD);
418    const PCODE = this.id.PCODE;
419
420    function dbg_op(label)
421    {
422        return {
423            "label": label,
424            "bytes": 0,
425            "checksum": new Uint16Array(1),
426            "firstAddr": null,
427            "lastAddr": null,
428            "suffix": ""
429        };
430    }
431
432    function debug_record(op, addr, d8, suffix)
433    {
434        suffix = suffix || "";
435        if(dbgCurrent && (dbgCurrent != op || dbgOps[dbgCurrent].suffix != suffix)) debug_flush();
436        var o = dbgOps[op];
437        if(!o) return;
438        if(!dbgCurrent)
439        {
440            dbgCurrent = op;
441            o.firstAddr = addr;
442            o.suffix = suffix;
443        }
444        o.bytes++;
445        o.lastAddr = addr;
446        o.checksum[0] = (o.checksum[0] + ((addr & 0xFFFF) ^ (d8 & 0xFF))) & 0xFFFF;          // Simple 16-bit rolling checksum over address and data.
447    }
448
449    function debug_flush()
450    {
451        if(!bDebug || !dbgCurrent) return;
452        var o = dbgOps[dbgCurrent];
453        if(o && o.bytes)
454        {
455            console.log(
456                PCODE+": "+o.label + o.suffix +
457                " bytes=" + o.bytes +
458                " range=$" + oCOM.getHexWord(o.firstAddr) + "-$" + oCOM.getHexWord(o.lastAddr) +
459                " checksum=$" + oCOM.getHexWord(o.checksum[0])
460            );
461        }
462        o.bytes = 0;
463        o.checksum[0] = 0;
464        o.firstAddr = null;
465        o.lastAddr = null;
466        o.suffix = "";
467        dbgCurrent = null;
468    }
469
470    function debug_updateMemoryMap(bRamcardActive)
471    {
472        console.log(PCODE+": updateMemoryMap() -> SWITCH "+(bRamcardActive?"ON":"OFF"));
473        if(bRamcardActive == false)
474        {
475            console.log(  PCODE+": BANK A: 0x"+oCOM.crc16(RAMCARD_MEM.slice(0, BANK_SIZE)).toString(16).toUpperCase()
476                        +" BANK B: 0x"+oCOM.crc16(RAMCARD_MEM.slice(BANK_SIZE, BANK_TOT_SIZE)).toString(16).toUpperCase()
477                        +" CONT: 0x"+oCOM.crc16(RAMCARD_MEM.slice(BANK_TOT_SIZE, TOTAL_SIZE)).toString(16).toUpperCase()
478                        +" STEP "+(logStep++));
479        }
480    }
481
482    function debug_soft_switch(sw,state)
483    {
484        var addr = state.softswitch_pos;
485        if(sw.WE==1)
486        {
487            if(state.RR==1) console.log(PCODE+": SOFTSWITCH $"+oCOM.getHexByte(addr)+" -> "+JSON.stringify(sw));
488            else console.log(PCODE+": waiting for RR to write-enable ($"+oCOM.getHexByte(addr)+")");
489        }
490        else console.log(PCODE+": SOFTSWITCH $"+oCOM.getHexByte(addr)+" -> "+JSON.stringify(sw));
491    }
492
493    this.deviceToolSlotHTML = function(ctx)
494    {
495        ctx = ctx || {};
496        var slotN = Number(ctx.slotN);
497        return ""
498            + "<div class=toolbox id=\""+(ctx.toolboxID || ("device_tool_"+ctx.slotID))+"\" hidden>"
499            + "  <div class=appbox style=\"height:76px;padding:0px 6px 0px 6px;\" title=\"Memory map\">"
500            + "    <div style=\"float:left;width:28px;text-align:center\">MEM<br><button class=appbut><i class=\"fa fa-sync-alt\" id=\"MEM_monitoring\" onclick=\""
501            + "oCOM.POPUP.toggle_class(this,'fa-stop-circle','fa-sync-alt');"
502            + "var bMEM_monitoring=oCOM.toggleRefreshEvent('MEM_monitoring');"
503            + "apple2plus.hwObj().enable_MEM_monitoring(bMEM_monitoring);"
504            + "oCOM.enableRefreshEvent('MEM_monitoring_MS16K',bMEM_monitoring);"
505            + "apple2plus.hwObj().io.SLOT2obj("+slotN+")?.enable_MEM_monitoring(bMEM_monitoring);"
506            + "\"></i></button><br>"
507            + "      <button class=appbut id=\"RAM64K_import\""
508            + " title=\"Load ram-64k.bin into Apple II main RAM $0000-$BFFF\""
509            + " onclick=\"document.getElementById('RAM64K_file').
509click()\">"
510            + "        <i class=\"fa fa-cloud-upload-alt\"></i></button>"
511            + "      <input id=\"RAM64K_file\" type=\"file\" accept=\".bin,application/octet-stream\" hidden onchange=\"EMU_loadRAM64KFile(this)\"></div>"
512            + "    <div id=\"EMU_mem_map\" style=\"margin-left:30px;white-space:nowrap\"></div>"
513            + "  </div>"
514            + "</div>";
515    };
516
517}
518
519
520
521
522/*  EXAMPLE CODE
523
524M.PRNTYX  EQU $F940   ; print hex word (Y=hi X=lo)
525M.OUTSP   EQU $DB57   ; space
526
527ORG $6000
528
529; WRITE BANK2 and RAMCARD in mode 3 :o)
530LDA $C083   ; 3: {"RE":true,"WE":true,"BANK":2}
531LDA $C083   ; 3: {"RE":true,"WE":true,"BANK":2}
532LDA #$12
533STA $E000   ; WRITE IN RAMCARD
534LDA #$34
535STA $D000   ; WRITE IN BANK 2
536LDX $D000
537LDY $E000
538LDA $C081   ; 1: {"WE":true,"BANK":2}
539LDA $C081   ; 1: {"WE":true,"BANK":2}
540JSR M.PRNTYX
541JSR M.OUTSP
542
543; WRITE BANK2 and RAMCARD in mode 2 :o(
544LDA $C082   ; 2: {"BANK":2}
545LDA #$23
546STA $E000   ; WRITE IN RAMCARD
547LDA #$45
548STA $D000   ; WRITE IN BANK 2
549LDX $D000
550LDY $E000
551LDA $C081   ; 1: {"WE":true,"BANK":2}
552LDA $C081   ; 1: {"WE":true,"BANK":2}
553JSR M.PRNTYX
554JSR M.OUTSP
555
556; WRITE BANK2 and RAMCARD in mode 1 :o)
557LDA $C081   ; 1: {"WE":true,"BANK":2}
558LDA $C081   ; 1: {"WE":true,"BANK":2}
559LDA #$34
560STA $E000   ; WRITE IN RAMCARD
561LDA #$56
562STA $D000   ; WRITE IN BANK 2
563LDX $D000
564LDY $E000
565JSR M.PRNTYX
566JSR M.OUTSP
567
568; WRITE BANK2 and RAMCARD in mode 0 :o(
569LDA $C080   ; 0: {"RE":true,"BANK":2}
570LDA #$45
571STA $D000   ; WRITE IN BANK 2
572LDA #$67
573STA $E000   ; WRITE IN RAMCARD
574LDX $D000
575LDY $E000
576LDA $C081   ; 1: {"WE":true,"BANK":2}
577LDA $C081   ; 1: {"WE":true,"BANK":2}
578JSR M.PRNTYX
579JSR M.OUTSP
580
581RTS
582
583
584CALL-151
5856000: AD 83 C0 AD 83 C0 A9 12 
5866008: 8D 00 E0 A9 34 8D 00 D0 
5876010: AE 00 D0 AC 00 E0 AD 81 
5886018: C0 AD 81 C0 20 40 F9 20 
5896020: 57 DB AD 82 C0 A9 23 8D 
5906028: 00 E0 A9 45 8D 00 D0 AE 
5916030: 00 D0 AC 00 E0 AD 81 C0 
5926038: AD 81 C0 20 40 F9 20 57 
5936040: DB AD 81 C0 AD 81 C0 A9 
5946048: 34 8D 00 E0 A9 56 8D 00 
5956050: D0 AE 00 D0 AC 00 E0 20 
5966058: 40 F9 20 57 DB AD 80 C0 
5976060: A9 45 8D 00 D0 A9 67 8D 
5986068: 00 E0 AE 00 D0 AC 00 E0 
5996070: AD 81 C0 AD 81 C0 20 40 
6006078: F9 20 57 DB 60 
601*/

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.