jszm.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  1. /*
  2. JSZM - JavaScript implementation of Z-machine
  3. This program is in public domain.
  4. Documentation:
  5. The exported function called JSZM is the constructor, which takes a
  6. Uint8Array as input. You can also use JSZM.Version for the version
  7. number which is object with properties: major, minor, subminor,
  8. timestamp. Properties of JSZM instances are:
  9. .highlight(fixpitch) = A generator function you define, which will be
  10. called to update the highlighting mode, which is fixpitch (if the
  11. argument is true) or normal (if argument is false). (You don't have to
  12. set it if you aren't implementing variable pitch by default.)
  13. .isTandy = A boolean, normally false. Set it to true to tell the game
  14. that it is a Tandy computer; this affects some games.
  15. .print(text,scripting) = A generator function that you must define, and
  16. will be called to print text. You must implement wrapping and buffering
  17. and scripting yourself. The second argument is true if it should be
  18. copied to the transcript or false if it should not be.
  19. .read(maxlen) = A generator function which you must define yourself, and
  20. which should return a string containing the player's input. Called when
  21. a READ instruction is executed; the argument is the maximum number of
  22. characters that are allowed (if you return a longer string, it will be
  23. truncated).
  24. .restarted() = A generator function you can optionally define. When the
  25. game starts or if restarted (with the RESTART instruction), it will be
  26. called after memory is initialized but before executing any more.
  27. .restore() = A generator function you can define yourself, which is
  28. called when restoring a saved game. Return a Uint8Array with the same
  29. contents passed to save() if successful, or you can return false or null
  30. or undefined if it failed.
  31. .run() = A generator function. Call it to run the program from the
  32. beginning, and call the next() method of the returned object to begin
  33. and to continue. This generator may call your own generator functions
  34. which may yield; it doesn't otherwise yield by itself. You must set up
  35. the other methods before calling run so that it can properly set up the
  36. contents of the Z-machine mode byte. This generator only finishes when a
  37. QUIT instruction is executed.
  38. .save(buf) = A generator function you can define yourself, and is called
  39. when saving the game. The argument is a Uint8Array, and you should
  40. attempt to save its contents somewhere, and then return true if
  41. successful or false if it failed.
  42. .serial = The serial number of the story file, as six ASCII characters.
  43. .screen(window) = Normally null. You can set it to a generator function
  44. which will be called when the SCREEN opcode is executed if you want to
  45. implement split screen.
  46. .split(height) = Normally null. You can set it to a generator function
  47. which will be called when the SPLIT opcode is executed if you want to
  48. implement split screen.
  49. .statusType = False for score/moves and true for hours/minutes. Use this
  50. to determine the meaning of arguments to updateStatusLine.
  51. .updateStatusLine(text,v18,v17) = Normally null, but can be a generator
  52. function if you are implementing the status line. It is called when a
  53. READ or USL instruction is executed. See statusType for the meaning of
  54. v18 and v17. Return value is unused.
  55. .verify() = A normal function. Calling it will attempt to verify the
  56. story file, and returns true if successful or false on error. You can
  57. override it with your own verification function if you want to.
  58. .zorkid = The ZORKID of the story file. This is what is normally
  59. displayed as the release number.
  60. */
  61. "use strict";
  62. const JSZM_Version={major:2,minor:0,subminor:3,timestamp:1518834833649};
  63. function JSZM(arr) {
  64. let mem;
  65. mem=this.memInit=new Uint8Array(arr);
  66. if(mem[0]!=3) throw new Error("Unsupported Z-code version.");
  67. this.byteSwapped=!!(mem[1]&1);
  68. this.statusType=!!(mem[1]&2);
  69. this.serial=String.fromCharCode(...mem.slice(18,24));
  70. this.zorkid=(mem[2]<<(this.byteSwapped?0:8))|(mem[3]<<(this.byteSwapped?8:0));
  71. }
  72. JSZM.prototype={
  73. byteSwapped: false,
  74. constructor: JSZM,
  75. deserialize: function(ar) {
  76. let e,i,j,ds,cs,pc,vi,purbot;
  77. let g8,g16s,g16,g24,g32;
  78. g8=()=>ar[e++];
  79. g16s=()=>(e+=2,vi.getInt16(e-2));
  80. g16=()=>(e+=2,vi.getUint16(e-2));
  81. g24=()=>(e+=3,vi.getUint32(e-4)&0xFFFFFF);
  82. g32=()=>(e+=4,vi.getUint32(e-4));
  83. try {
  84. e=purbot=this.getu(14);
  85. vi=new DataView(ar.buffer);
  86. if(ar[2]!=this.mem[2] || ar[3]!=this.mem[3]) return null; // ZORKID does not match
  87. pc=g32();
  88. cs=new Array(g16());
  89. ds=Array.from({length:g16()},g16s);
  90. for(i=0;i<cs.length;i++) {
  91. cs[i]={};
  92. cs[i].local=new Int16Array(g8());
  93. cs[i].pc=g24();
  94. cs[i].ds=Array.from({length:g16()},g16s);
  95. for(j=0;j<cs[i].local.length;j++) cs[i].local[j]=g16s();
  96. }
  97. this.mem.set(new Uint8Array(ar.buffer,0,purbot));
  98. return [ds,cs,pc];
  99. } catch(e) {
  100. return null;
  101. }
  102. },
  103. endText: 0,
  104. fwords: null,
  105. genPrint: function*(text) {
  106. var x=this.get(16);
  107. if(x!=this.savedFlags) {
  108. this.savedFlags=x;
  109. yield*this.highlight(!!(x&2));
  110. }
  111. yield*this.print(text,!!(x&1));
  112. },
  113. get: function(x) { return this.view.getInt16(x,this.byteSwapped); },
  114. getText: function(addr) {
  115. let d; // function to parse each Z-character
  116. let o=""; // output
  117. let ps=0; // permanent shift
  118. let ts=0; // temporary shift
  119. let w; // read each 16-bits data
  120. let y; // auxiliary data for parsing state
  121. d=v => {
  122. if(ts==3) {
  123. y=v<<5;
  124. ts=4;
  125. } else if(ts==4) {
  126. y+=v;
  127. if(y==13) o+="\n";
  128. else if(y) o+=String.fromCharCode(y);
  129. ts=ps;
  130. } else if(ts==5) {
  131. o+=this.getText(this.getu(this.fwords+(y+v)*2)*2);
  132. ts=ps;
  133. } else if(v==0) {
  134. o+=" ";
  135. } else if(v<4) {
  136. ts=5;
  137. y=(v-1)*32;
  138. } else if(v<6) {
  139. if(!ts) ts=v-3;
  140. else if(ts==v-3) ps=ts;
  141. else ps=ts=0;
  142. } else if(v==6 && ts==2) {
  143. ts=3;
  144. } else {
  145. o+="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ*\n0123456789.,!?_#'\"/\\-:()"[ts*26+v-6];
  146. ts=ps;
  147. }
  148. };
  149. for(;;) {
  150. w=this.getu(addr);
  151. addr+=2;
  152. d((w>>10)&31);
  153. d((w>>5)&31);
  154. d(w&31);
  155. if(w&32768) break;
  156. }
  157. this.endText=addr;
  158. return o;
  159. },
  160. getu: function(x) { return this.view.getUint16(x,this.byteSwapped); },
  161. handleInput: function(str,t1,t2) {
  162. let i,br,w;
  163. // Put text
  164. str=str.toLowerCase().slice(0,this.mem[t1]-1);
  165. for(i=0;i<str.length;i++) this.mem[t1+i+1]=str.charCodeAt(i);
  166. this.mem[t1+str.length+1]=0;
  167. // Lex text
  168. w=x=>(i=0,x.split("").filter(y => (i+=/[a-z]/.test(y)?1:/[0-9.,!?_#'"\/\\:\-()]/.test(y)?2:4)<7).join(""));
  169. br=JSON.parse("["+str.replace(this.regBreak,(m,o)=>",["+(m.length)+","+(this.vocabulary.get(w(m))||0)+","+(o+1)+"]").slice(1)+"]");
  170. i=this.mem[t2+1]=br.length;
  171. while(i--) {
  172. this.putu(t2+i*4+2,br[i][1]);
  173. this.mem[t2+i*4+4]=br[i][0];
  174. this.mem[t2+i*4+5]=br[i][2];
  175. }
  176. },
  177. highlight: ()=>[],
  178. isTandy: false,
  179. mem: null,
  180. memInit: null,
  181. parseVocab: function(s) {
  182. let e,n;
  183. this.vocabulary=new Map();
  184. if(!s) {
  185. this.selfInsertingBreaks="";
  186. this.regBreak=/[^ \n\t]+/g;
  187. return s;
  188. }
  189. n=this.mem[s++];
  190. e=this.selfInsertingBreaks=String.fromCharCode(...this.mem.slice(s,s+n));
  191. e=e.split("").map(x=>(x.toUpperCase()==x.toLowerCase()?"":"\\")+x).join("")+"]";
  192. this.regBreak=new RegExp("["+e+"|[^ \\n\\t"+e+"+","g");
  193. s+=n;
  194. e=this.mem[s++];
  195. n=this.get(s);
  196. s+=2;
  197. while(n--) {
  198. this.vocabulary.set(this.getText(s),s);
  199. s+=e;
  200. }
  201. return s;
  202. },
  203. print: ()=>[],
  204. put: function(x,y) { return this.view.setInt16(x,y,this.byteSwapped); },
  205. putu: function(x,y) { return this.view.setUint16(x,y&65535,this.byteSwapped); },
  206. read: ()=>[],
  207. regBreak: null,
  208. restarted: ()=>[],
  209. restore: ()=>[],
  210. run: function*() {
  211. let mem,pc,cs,ds,op0,op1,op2,op3,opc,inst,x,y,z;
  212. let globals,objects,fwords,defprop;
  213. let addr,fetch,flagset,init,move,opfetch,pcfetch,pcget,pcgetb,pcgetu,predicate,propfind,ret,store,xfetch,xstore;
  214. // Functions
  215. addr=(x) => (x&65535)<<1;
  216. fetch=(x) => {
  217. if(x==0) return ds.pop();
  218. if(x<16) return cs[0].local[x-1];
  219. return this.get(globals+2*x);
  220. };
  221. flagset=() => {
  222. op3=1<<(15&~op1);
  223. op2=objects+op0*9+(op1&16?2:0);
  224. opc=this.get(op2);
  225. };
  226. init=() => {
  227. mem=this.mem=new Uint8Array(this.memInit);
  228. this.view=new DataView(mem.buffer);
  229. mem[1]&=3;
  230. if(this.isTandy) mem[1]|=8;
  231. if(!this.updateStatusLine) mem[1]|=16;
  232. if(this.screen && this.split) mem[1]|=32;
  233. this.put(16,this.savedFlags);
  234. if(!this.vocabulary) this.parseVocab(this.getu(8));
  235. defprop=this.getu(10)-2;
  236. globals=this.getu(12)-32;
  237. this.fwords=fwords=this.getu(24);
  238. cs=[];
  239. ds=[];
  240. pc=this.getu(6);
  241. objects=defprop+55;
  242. };
  243. move=(x,y) => {
  244. var w,z;
  245. // Remove from old FIRST-NEXT chain
  246. if(z=mem[objects+x*9+4]) {
  247. if(mem[objects+z*9+6]==x) { // is x.loc.first=x?
  248. mem[objects+z*9+6]=mem[objects+x*9+5]; // x.loc.first=x.next
  249. } else {
  250. z=mem[objects+z*9+6]; // z=x.loc.first
  251. while(z!=x) {
  252. w=z;
  253. z=mem[objects+z*9+5]; // z=z.next
  254. }
  255. mem[objects+w*9+5]=mem[objects+x*9+5]; // w.next=x.next
  256. }
  257. }
  258. // Insert at beginning of new FIRST-NEXT chain
  259. if(mem[objects+x*9+4]=y) { // x.loc=y
  260. mem[objects+x*9+5]=mem[objects+y*9+6]; // x.next=y.first
  261. mem[objects+y*9+6]=x; // y.first=x
  262. } else {
  263. mem[objects+x*9+5]=0; // x.next=0
  264. }
  265. };
  266. opfetch=(x,y) => {
  267. if((x&=3)==3) return;
  268. opc=y;
  269. return [pcget,pcgetb,pcfetch][x]();
  270. };
  271. pcfetch=(x) => fetch(mem[pc++]);
  272. pcget=() => {
  273. pc+=2;
  274. return this.get(pc-2);
  275. };
  276. pcgetb=() => mem[pc++];
  277. pcgetu=() => {
  278. pc+=2;
  279. return this.getu(pc-2);
  280. };
  281. predicate=(p) => {
  282. var x=pcgetb();
  283. if(x&128) p=!p;
  284. if(x&64) x&=63; else x=((x&63)<<8)|pcgetb();
  285. if(p) return;
  286. if(x==0 || x==1) return ret(x);
  287. if(x&0x2000) x-=0x4000;
  288. pc+=x-2;
  289. };
  290. propfind=() => {
  291. var z=this.getu(objects+op0*9+7);
  292. z+=mem[z]*2+1;
  293. while(mem[z]) {
  294. if((mem[z]&31)==op1) {
  295. op3=z+1;
  296. return true;
  297. } else {
  298. z+=(mem[z]>>5)+2;
  299. }
  300. }
  301. op3=0;
  302. return false;
  303. };
  304. ret=(x) => {
  305. ds=cs[0].ds;
  306. pc=cs[0].pc;
  307. cs.shift();
  308. store(x);
  309. };
  310. store=(y) => {
  311. var x=pcgetb();
  312. if(x==0) ds.push(y);
  313. else if(x<16) cs[0].local[x-1]=y;
  314. else this.put(globals+2*x,y);
  315. };
  316. xfetch=(x) => {
  317. if(x==0) return ds[ds.length-1];
  318. if(x<16) return cs[0].local[x-1];
  319. return this.get(globals+2*x);
  320. };
  321. xstore=(x,y) => {
  322. if(x==0) ds[ds.length-1]=y;
  323. else if(x<16) cs[0].local[x-1]=y;
  324. else this.put(globals+2*x,y);
  325. };
  326. // Initializations
  327. init();
  328. yield*this.restarted();
  329. yield*this.highlight(!!(this.savedFlags&2));
  330. // Main loop
  331. main: for(;;) {
  332. inst=pcgetb();
  333. if(inst<128) {
  334. // 2OP
  335. if(inst&64) op0=pcfetch(); else op0=pcgetb();
  336. if(inst&32) op1=pcfetch(); else op1=pcgetb();
  337. inst&=31;
  338. opc=2;
  339. } else if(inst<176) {
  340. // 1OP
  341. x=(inst>>4)&3;
  342. inst&=143;
  343. if(x==0) op0=pcget();
  344. else if(x==1) op0=pcgetb();
  345. else if(x==2) op0=pcfetch();
  346. } else if(inst>=192) {
  347. // EXT
  348. x=pcgetb();
  349. op0=opfetch(x>>6,1);
  350. op1=opfetch(x>>4,2);
  351. op2=opfetch(x>>2,3);
  352. op3=opfetch(x>>0,4);
  353. if(inst<224) inst&=31;
  354. }
  355. switch(inst) {
  356. case 1: // EQUAL?
  357. predicate(op0==op1 || (opc>2 && op0==op2) || (opc==4 && op0==op3));
  358. break;
  359. case 2: // LESS?
  360. predicate(op0<op1);
  361. break;
  362. case 3: // GRTR?
  363. predicate(op0>op1);
  364. break;
  365. case 4: // DLESS?
  366. xstore(op0,x=xfetch(op0)-1);
  367. predicate(x<op1);
  368. break;
  369. case 5: // IGRTR?
  370. xstore(op0,x=xfetch(op0)+1);
  371. predicate(x>op1);
  372. break;
  373. case 6: // IN?
  374. predicate(mem[objects+op0*9+4]==op1);
  375. break;
  376. case 7: // BTST
  377. predicate((op0&op1)==op1);
  378. break;
  379. case 8: // BOR
  380. store(op0|op1);
  381. break;
  382. case 9: // BAND
  383. store(op0&op1);
  384. break;
  385. case 10: // FSET?
  386. flagset();
  387. predicate(opc&op3);
  388. break;
  389. case 11: // FSET
  390. flagset();
  391. this.put(op2,opc|op3);
  392. break;
  393. case 12: // FCLEAR
  394. flagset();
  395. this.put(op2,opc&~op3);
  396. break;
  397. case 13: // SET
  398. xstore(op0,op1);
  399. break;
  400. case 14: // MOVE
  401. move(op0,op1);
  402. break;
  403. case 15: // GET
  404. store(this.get((op0+op1*2)&65535));
  405. break;
  406. case 16: // GETB
  407. store(mem[(op0+op1)&65535]);
  408. break;
  409. case 17: // GETP
  410. if(propfind()) store(mem[op3-1]&32?this.get(op3):mem[op3]);
  411. else store(this.get(defprop+2*op1));
  412. break;
  413. case 18: // GETPT
  414. propfind();
  415. store(op3);
  416. break;
  417. case 19: // NEXTP
  418. if(op1) {
  419. // Return next property
  420. propfind();
  421. store(mem[op3+(mem[op3-1]>>5)+1]&31);
  422. } else {
  423. // Return first property
  424. x=this.getu(objects+op0*9+7);
  425. store(mem[x+mem[x]*2+1]&31);
  426. }
  427. break;
  428. case 20: // ADD
  429. store(op0+op1);
  430. break;
  431. case 21: // SUB
  432. store(op0-op1);
  433. break;
  434. case 22: // MUL
  435. store(Math.imul(op0,op1));
  436. break;
  437. case 23: // DIV
  438. store(Math.trunc(op0/op1));
  439. break;
  440. case 24: // MOD
  441. store(op0%op1);
  442. break;
  443. case 128: // ZERO?
  444. predicate(!op0);
  445. break;
  446. case 129: // NEXT?
  447. store(x=mem[objects+op0*9+5]);
  448. predicate(x);
  449. break;
  450. case 130: // FIRST?
  451. store(x=mem[objects+op0*9+6]);
  452. predicate(x);
  453. break;
  454. case 131: // LOC
  455. store(mem[objects+op0*9+4]);
  456. break;
  457. case 132: // PTSIZE
  458. store((mem[(op0-1)&65535]>>5)+1);
  459. break;
  460. case 133: // INC
  461. x=xfetch(op0);
  462. xstore(op0,x+1);
  463. break;
  464. case 134: // DEC
  465. x=xfetch(op0);
  466. xstore(op0,x-1);
  467. break;
  468. case 135: // PRINTB
  469. yield*this.genPrint(this.getText(op0&65535));
  470. break;
  471. case 137: // REMOVE
  472. move(op0,0);
  473. break;
  474. case 138: // PRINTD
  475. yield*this.genPrint(this.getText(this.getu(objects+op0*9+7)+1));
  476. break;
  477. case 139: // RETURN
  478. ret(op0);
  479. break;
  480. case 140: // JUMP
  481. pc+=op0-2;
  482. break;
  483. case 141: // PRINT
  484. yield*this.genPrint(this.getText(addr(op0)));
  485. break;
  486. case 142: // VALUE
  487. store(xfetch(op0));
  488. break;
  489. case 143: // BCOM
  490. store(~op0);
  491. break;
  492. case 176: // RTRUE
  493. ret(1);
  494. break;
  495. case 177: // RFALSE
  496. ret(0);
  497. break;
  498. case 178: // PRINTI
  499. yield*this.genPrint(this.getText(pc));
  500. pc=this.endText;
  501. break;
  502. case 179: // PRINTR
  503. yield*this.genPrint(this.getText(pc)+"\n");
  504. ret(1);
  505. break;
  506. case 180: // NOOP
  507. break;
  508. case 181: // SAVE
  509. this.savedFlags=this.get(16);
  510. predicate(yield*this.save(this.serialize(ds,cs,pc)));
  511. break;
  512. case 182: // RESTORE
  513. this.savedFlags=this.get(16);
  514. if(z=yield*this.restore()) z=this.deserialize(z);
  515. this.put(16,this.savedFlags);
  516. if(z) ds=z[0],cs=z[1],pc=z[2];
  517. predicate(z);
  518. break;
  519. case 183: // RESTART
  520. init();
  521. yield*this.restarted();
  522. break;
  523. case 184: // RSTACK
  524. ret(ds[ds.length-1]);
  525. break;
  526. case 185: // FSTACK
  527. ds.pop();
  528. break;
  529. case 186: // QUIT
  530. return;
  531. case 187: // CRLF
  532. yield*this.genPrint("\n");
  533. break;
  534. case 188: // USL
  535. if(this.updateStatusLine) yield*this.updateStatusLine(this.getText(this.getu(objects+xfetch(16)*9+7)+1),xfetch(18),xfetch(17));
  536. break;
  537. case 189: // VERIFY
  538. predicate(this.verify());
  539. break;
  540. case 224: // CALL
  541. if(op0) {
  542. x=mem[op0=addr(op0)];
  543. cs.unshift({ds:ds,pc:pc,local:new Int16Array(x)});
  544. ds=[];
  545. pc=op0+1;
  546. for(x=0;x<mem[op0];x++) cs[0].local[x]=pcget();
  547. if(opc>1 && mem[op0]>0) cs[0].local[0]=op1;
  548. if(opc>2 && mem[op0]>1) cs[0].local[1]=op2;
  549. if(opc>3 && mem[op0]>2) cs[0].local[2]=op3;
  550. } else {
  551. store(0);
  552. }
  553. break;
  554. case 225: // PUT
  555. this.put((op0+op1*2)&65535,op2);
  556. break;
  557. case 226: // PUTB
  558. mem[(op0+op1)&65535]=op2;
  559. break;
  560. case 227: // PUTP
  561. propfind();
  562. if(mem[op3-1]&32) this.put(op3,op2);
  563. else mem[op3]=op2;
  564. break;
  565. case 228: // READ
  566. yield*this.genPrint("");
  567. if(this.updateStatusLine) yield*this.updateStatusLine(this.getText(this.getu(objects+xfetch(16)*9+7)+1),xfetch(18),xfetch(17));
  568. this.handleInput(yield*this.read(mem[op0&65535]),op0&65535,op1&65535);
  569. break;
  570. case 229: // PRINTC
  571. yield*this.genPrint(op0==13?"\n":op0?String.fromCharCode(op0):"");
  572. break;
  573. case 230: // PRINTN
  574. yield*this.genPrint(String(op0));
  575. break;
  576. case 231: // RANDOM
  577. store(op0>0?Math.floor(Math.random()*op0)+1:0);
  578. break;
  579. case 232: // PUSH
  580. ds.push(op0);
  581. break;
  582. case 233: // POP
  583. xstore(op0,ds.pop());
  584. break;
  585. case 234: // SPLIT
  586. if(this.split) yield*this.split(op0);
  587. break;
  588. case 235: // SCREEN
  589. if(this.screen) yield*this.screen(op0);
  590. break;
  591. default:
  592. throw new Error("JSZM: Invalid Z-machine opcode");
  593. }
  594. }
  595. },
  596. save: ()=>[],
  597. savedFlags: 0,
  598. selfInsertingBreaks: null,
  599. serial: null,
  600. serialize: function(ds,cs,pc) {
  601. let i,j,e,ar,vi;
  602. e=this.getu(14); // PURBOT
  603. i=e+cs.reduce((p,c)=>p+2*(c.ds.length+c.local.length)+6,0)+2*ds.length+8;
  604. ar=new Uint8Array(i);
  605. ar.set(new Uint8Array(this.mem.buffer,0,e));
  606. vi=new DataView(ar.buffer);
  607. vi.setUint32(e,pc);
  608. vi.setUint16(e+4,cs.length);
  609. vi.setUint16(e+6,ds.length);
  610. for(i=0;i<ds.length;i++) vi.setInt16(e+i*2+8,ds[i]);
  611. e+=ds.length*2+8;
  612. for(i=0;i<cs.length;i++) {
  613. vi.setUint32(e,cs[i].pc);
  614. vi.setUint8(e,cs[i].local.length);
  615. vi.setUint16(e+4,cs[i].ds.length);
  616. for(j=0;j<cs[i].ds.length;j++) vi.setInt16(e+j*2+6,cs[i].ds[j]);
  617. for(j=0;j<cs[i].local.length;j++) vi.setInt16(e+cs[i].ds.length*2+j*2+6,cs[i].local[j]);
  618. e+=(cs[i].ds.length+cs[i].local.length)*2+6;
  619. }
  620. return ar;
  621. },
  622. screen: null,
  623. split: null,
  624. statusType: null,
  625. updateStatusLine: null,
  626. verify: function() {
  627. let plenth=this.getu(26);
  628. let pchksm=this.getu(28);
  629. let i=64;
  630. while(i<plenth*2) pchksm=(pchksm-this.memInit[i++])&65535;
  631. return !pchksm;
  632. },
  633. view: null,
  634. vocabulary: null,
  635. zorkid: null,
  636. };
  637. JSZM.version=JSZM_Version;
  638. try {
  639. if(module && module.exports) module.exports=JSZM;
  640. } catch(e) {}