@nataliapc/mcp-openmsx 1.1.5 → 1.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +38 -0
  2. package/dist/server.js +110 -23
  3. package/dist/utils.js +17 -0
  4. package/package.json +4 -1
  5. package/resources/audio/toc.json +31 -0
  6. package/resources/bios/Calling_BIOS_from_MSX-DOS.md +75 -0
  7. package/resources/bios/MSX2_SUBROM_BIOS_calls.md +734 -0
  8. package/resources/bios/MSX_BIOS_calls.md +1046 -0
  9. package/resources/bios/toc.json +24 -0
  10. package/resources/book--msx2-technical-handbook/Appendix1__BIOS_Listing.md +1464 -0
  11. package/resources/book--msx2-technical-handbook/Appendix2__Math-Pack.md +427 -0
  12. package/resources/book--msx2-technical-handbook/Appendix3__Bit_Block_Transfer.md +182 -0
  13. package/resources/book--msx2-technical-handbook/Appendix4__Work_Area_Listing.md +1637 -0
  14. package/resources/book--msx2-technical-handbook/Appendix5__VRAM_Map.md +145 -0
  15. package/resources/book--msx2-technical-handbook/Appendix6__IO_Map.md +128 -0
  16. package/resources/book--msx2-technical-handbook/Appendix8_10__Control_Codes_and_Escape_Sequences.md +76 -0
  17. package/resources/book--msx2-technical-handbook/Chapter1__MSX_System_Overview.md +402 -0
  18. package/resources/book--msx2-technical-handbook/Chapter2__BASIC.md +2148 -0
  19. package/resources/book--msx2-technical-handbook/Chapter3__MSX-DOS.md +2577 -0
  20. package/resources/book--msx2-technical-handbook/Chapter4a__VDP_and_Display_Screen.md +2052 -0
  21. package/resources/book--msx2-technical-handbook/Chapter4b__VDP_and_Display_Screen.md +3311 -0
  22. package/resources/book--msx2-technical-handbook/Chapter5a__Access_to_Peripherals_through_BIOS.md +2714 -0
  23. package/resources/book--msx2-technical-handbook/Chapter5b__Access_to_Peripherals_through_BIOS.md +1263 -0
  24. package/resources/book--msx2-technical-handbook/MSX_Kun_BASIC_Compiler.md +220 -0
  25. package/resources/book--msx2-technical-handbook/toc.json +82 -0
  26. package/resources/book--the-msx-red-book/the_msx_red_book.md +10349 -0
  27. package/resources/book--the-msx-red-book/toc.json +12 -0
  28. package/resources/msx-dos/MSX-DOS_2_Function_Specifications.md +1366 -0
  29. package/resources/msx-dos/MSX-DOS_2_Program_Interface_Specification.md +963 -0
  30. package/resources/msx-dos/toc.json +18 -0
  31. package/resources/msx-unapi/Ethernet_UNAPI_specification_1.1.md +369 -0
  32. package/resources/msx-unapi/Introduction_to_MSX-UNAPI.md +132 -0
  33. package/resources/msx-unapi/MSX_UNAPI_specification_1.1.md +679 -0
  34. package/resources/msx-unapi/TCP-IP_UNAPI_specification.md +2361 -0
  35. package/resources/msx-unapi/toc.json +27 -0
  36. package/resources/others/toc.json +11 -0
  37. package/resources/processors/Z80_R800_instruction_set.md +482 -0
  38. package/resources/processors/toc.json +24 -0
  39. package/resources/processors/z80-undocumented.tex +5617 -0
  40. package/resources/processors/z80_detailed_instruction_set.md +2025 -0
  41. package/resources/programming/toc.json +121 -0
  42. package/resources/system/MSX_IO_ports_overview.md +554 -0
  43. package/resources/system/toc.json +18 -0
  44. package/resources/video/V9938_Technical_Data_Book.md +3623 -0
  45. package/resources/video/V9958_Technical_Data_Book.md +417 -0
  46. package/resources/video/V9990_Programmers_Manual_Banzai.html +1582 -0
  47. package/resources/video/VDP_TMS9918A.txt +709 -0
  48. package/resources/video/toc.json +28 -0
package/README.md CHANGED
@@ -65,6 +65,44 @@ The MCP server translates high-level commands from your Copilot AI into `TCL` co
65
65
  - `screen_shot`: Capture emulator screen: _`as_image`, `to_file`_.
66
66
  - `screen_dump`: Export screen data as BASIC BSAVE instruction.
67
67
 
68
+ ## 📚 Available MCP Resources
69
+
70
+ ### What are MCP Resources?
71
+
72
+ MCP resources are structured data sets, documentation, and helper files that extend the capabilities of the MCP server. They provide essential information such as machine definitions, extension lists, media templates, and programming examples, enabling more powerful automation, testing, and development workflows for MSX software within the MCP-openMSX environment.
73
+
74
+ ### Available Resources
75
+
76
+ There are more than 60 resources available, some included directly in the MCP and others accessible via download when queried. They are organized into the following categories:
77
+
78
+ - `Processors` (Z80, R800)
79
+ - `Bios` (Bios ROM, DOS ROM, SUBROM, ...)
80
+ - `System`
81
+ - `Audio`
82
+ - `Video`
83
+ - `Programming` (ASM, BASIC, ...)
84
+ - `MSX-DOS`
85
+ - `MSX-UNAPI`
86
+
87
+ And two books:
88
+
89
+ - `MSX2 Technical Handbook`
90
+ - `The MSX Red Book`
91
+
92
+ ### Resources from:
93
+
94
+ - [Grauw MSX Assembly Page](https://map.grauw.nl/)
95
+ - [Z80 Heaven Wiki](http://z80-heaven.wikidot.com/)
96
+ - [The MSX Red Book](https://github.com/gseidler/The-MSX-Red-Book)
97
+ - [MSX2 Technical Handbook](https://github.com/Konamiman/MSX2-Technical-Handbook)
98
+ - [Konamiman MSX-UNAPI-specification](https://github.com/Konamiman/MSX-UNAPI-specification)
99
+ - [BiFi MSX Net](http://bifi.msxnet.org/msxnet/)
100
+ - [MSX Wiki](https://www.msx.org/wiki/Main_Page)
101
+ - [MSX Banzai!](http://msxbanzai.tni.nl/)
102
+
103
+ Thanks to the authors of these resources, who have made them available under various licenses. This MCP server includes some of these resources to enhance the development experience.
104
+
105
+ The rights to these resources belong to their respective authors and are distributed under the licenses they have defined.
68
106
 
69
107
  ## 🚀 Quick Start
70
108
 
package/dist/server.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * through TCL commands via stdio.
7
7
  *
8
8
  * @package @nataliapc/mcp-openmsx
9
- * @version 1.1.4
9
+ * @version 1.1.8
10
10
  * @author Natalia Pujol Cremades (@nataliapc)
11
11
  * @license GPL2
12
12
  */
@@ -18,11 +18,13 @@ import { randomUUID } from "node:crypto";
18
18
  import { z } from "zod";
19
19
  import express from "express";
20
20
  import fs from "fs/promises";
21
+ import mime from "mime-types";
21
22
  import path from "path";
22
23
  import { openMSXInstance } from "./openmsx.js";
23
- import { encodeTypeText, isErrorResponse } from "./utils.js";
24
+ import { encodeTypeText, isErrorResponse, getResponseContent } from "./utils.js";
24
25
  // Version info for CLI
25
- const PACKAGE_VERSION = "1.1.4";
26
+ const PACKAGE_VERSION = "1.1.8";
27
+ const resourcesDir = path.join(path.dirname(new URL(import.meta.url).pathname), "../resources");
26
28
  // Defaults for openMSX paths
27
29
  var OPENMSX_EXECUTABLE = 'openmsx';
28
30
  var OPENMSX_SHARE_DIR = '/usr/share/openmsx';
@@ -35,7 +37,7 @@ var EXTENSIONS_DIR = `${OPENMSX_SHARE_DIR}/extensions`;
35
37
  // Tools available in the MCP server
36
38
  // https://modelcontextprotocol.io/docs/concepts/tools#tool-definition-structure
37
39
  //
38
- function registerAllTools(server) {
40
+ async function registerAllTools(server) {
39
41
  server.tool(
40
42
  // Name of the tool (used to call it)
41
43
  "emu_control",
@@ -334,15 +336,15 @@ function registerAllTools(server) {
334
336
  'getStackPile': to get an overview of the CPU stack.
335
337
  'disassemble [address] [size]': to print disassembled instructions at the address parameter location or PC register if empty.
336
338
  'getActiveCpu': to return the active cpu: z80 or r800.
337
- "**Important Note**: Addresses and values are in hexadecimal format (e.g. 0x0000)."
339
+ "**Important Note**: Addresses and values are in hexadecimal format (e.g. 0xd2 0x3af2)."
338
340
  `,
339
341
  // Schema for the tool (input validation)
340
342
  {
341
343
  command: z.enum(["getCpuRegisters", "getRegister", "setRegister", "getStackPile", "disassemble", "getActiveCpu"]),
342
344
  register: z.enum(["pc", "sp", "ix", "iy", "af", "bc", "de", "hl", "ixh", "ixl", "iyh", "iyl", "a", "f", "b", "c", "d", "e", "h", "l", "i", "r", "im", "iff"]).optional(),
343
- address: z.string().regex(/^0x[0-9a-fA-F]{4}$/), // 4 hex digits for MSX memory address
344
- value: z.string().regex(/^0x[0-9a-fA-F]{2-4}$/).optional(), // 2-4 hex digits for byte value for writeByte command
345
- size: z.number().min(1).max(50).optional().default(8), // Number of bytes for disassemble command
345
+ address: z.string().regex(/^0x[0-9a-fA-F]{4}$/).optional(), // 4 hex digits for MSX memory address
346
+ value: z.string().regex(/^0x[0-9a-fA-F]{2,4}$/).optional(), // 2-4 hex digits for byte value for writeByte command
347
+ size: z.number().min(8).max(50).optional(), // Number of bytes for disassemble command
346
348
  },
347
349
  // Handler for the tool (function to be executed when the tool is called)
348
350
  async ({ command, address, register, value, size }) => {
@@ -674,7 +676,7 @@ function registerAllTools(server) {
674
676
  switch (command) {
675
677
  case "as_image":
676
678
  try {
677
- // Check if the response is a file path using fstat
679
+ // Check if the response is a file path
678
680
  if (!response || !response.startsWith(OPENMSX_SCREENSHOT_DIR) || !response.endsWith('.png')) {
679
681
  throw new Error(`Invalid screenshot "${response}"`);
680
682
  }
@@ -745,6 +747,7 @@ function registerAllTools(server) {
745
747
  'deleteProgramLines <startLine> [endline]': deletes a specific line range from the current BASIC program, if endline is not specified, only the startLine is deleted.
746
748
  **Important Note**: priorize this tools to develop BASIC programs, it's more efficient than using the 'sendText' tool.
747
749
  **Important Note**: all the program lines must be ended with a carriage return (\\r) to be correctly processed, even the last one.
750
+ **Important Note**: if you have doubts about MSX BASIC, use the resources provided by this MCP.
748
751
  `,
749
752
  // Schema for the tool (input validation)
750
753
  {
@@ -824,17 +827,101 @@ function registerAllTools(server) {
824
827
  response !== undefined ? response : `Error: No response for command "${command}".`
825
828
  ]);
826
829
  });
830
+ // ============================================================================
831
+ // MSX Documentation resources
832
+ const resdocs = (await listResourcesDirectory()).sort();
833
+ for (let index = 0; index < resdocs.length; index++) {
834
+ const sectionName = resdocs[index];
835
+ const tocFile = path.join(resourcesDir, `${sectionName}/toc.json`);
836
+ const tocContent = JSON.parse(await fs.readFile(tocFile, 'utf8'));
837
+ tocContent.toc.forEach((item, itemIndex) => {
838
+ const itemName = item.uri.split('/').pop() || '';
839
+ server.resource(
840
+ // Name of the resource (used to call it)
841
+ `msxdocs_${sectionName}_${itemName}`,
842
+ // Resource URI template
843
+ item.uri,
844
+ // Metadata for the resource
845
+ {
846
+ title: item.title || `MSX Documentation '${sectionName}' - ${itemName}`,
847
+ description: item.description || `Documentation for MSX resource '${sectionName}' - ${itemName}`,
848
+ mimeType: item.mimeType || 'text/markdown',
849
+ },
850
+ // Handler for the resource (function to be executed when the resource is called)
851
+ async (uri) => {
852
+ let resourceContent;
853
+ let mimeType;
854
+ if (uri.href.startsWith('http://') || uri.href.startsWith('https://')) {
855
+ // Fetch the resource from the URL
856
+ try {
857
+ resourceContent = await fetch(uri.href).then(response => {
858
+ mimeType = response.headers.get('content-type') || 'text/plain';
859
+ return response.text();
860
+ }) || 'Error downloading resource content';
861
+ }
862
+ catch (error) {
863
+ // Throw exception (MCP protocol requirement)
864
+ throw new Error(`Error fetching resource from ${uri.href}: ${error instanceof Error ? error.message : String(error)}`);
865
+ }
866
+ }
867
+ else {
868
+ // Read the resource from the local MCP server resources directory
869
+ try {
870
+ let resourceFile;
871
+ [mimeType, resourceFile] = await addFileExtension(path.join(resourcesDir, `${sectionName}/${itemName}`));
872
+ resourceContent = await fs.readFile(resourceFile, 'utf8');
873
+ }
874
+ catch (error) {
875
+ // Throw exception (MCP protocol requirement)
876
+ throw new Error(`Error reading resource ${sectionName}/${item.uri}: ${error instanceof Error ? error.message : String(error)}`);
877
+ }
878
+ }
879
+ return {
880
+ contents: [{
881
+ uri: uri.href,
882
+ text: resourceContent,
883
+ mimeType: mimeType || 'text/plain',
884
+ }],
885
+ };
886
+ });
887
+ });
888
+ }
889
+ ;
827
890
  }
828
- function getResponseContent(response, isError = false) {
829
- // Check if any response line starts with "Error:" to automatically set isError to true
830
- const hasError = isError || response.some(line => isErrorResponse(line));
831
- return {
832
- content: response.map(line => ({
833
- type: "text",
834
- text: line == '' ? "Ok" : line,
835
- })),
836
- isError: hasError
837
- };
891
+ async function addFileExtension(filePath) {
892
+ // Get directory and filename
893
+ const directory = path.dirname(filePath);
894
+ const filename = path.basename(filePath);
895
+ try {
896
+ // Get all files in directory that start with our filename
897
+ const files = await fs.readdir(directory);
898
+ const matchingFiles = files.filter(file => file.startsWith(filename));
899
+ if (matchingFiles.length > 0) {
900
+ const fileFound = path.join(directory, matchingFiles[0]);
901
+ return [
902
+ mime.lookup(fileFound) || 'text/plain',
903
+ fileFound
904
+ ];
905
+ }
906
+ }
907
+ catch (error) {
908
+ console.error('Error reading directory:', error);
909
+ }
910
+ // Return original if no matches found
911
+ return ['text/plain', filePath];
912
+ }
913
+ async function listResourcesDirectory() {
914
+ try {
915
+ const directories = await fs.readdir(resourcesDir, { withFileTypes: true });
916
+ const folderNames = directories
917
+ .filter(dirent => dirent.isDirectory())
918
+ .map(dirent => dirent.name);
919
+ return folderNames;
920
+ }
921
+ catch (error) {
922
+ console.error("Error reading resources directory:", error);
923
+ return [];
924
+ }
838
925
  }
839
926
  // ============================================================================
840
927
  // Cleanup handlers for graceful shutdown of MCP server
@@ -933,7 +1020,7 @@ async function startHttpServer() {
933
1020
  }
934
1021
  };
935
1022
  // Create a new server instance for this session
936
- const httpServer = createServerInstance();
1023
+ const httpServer = await createServerInstance();
937
1024
  await httpServer.connect(transport);
938
1025
  }
939
1026
  else {
@@ -961,14 +1048,14 @@ async function startHttpServer() {
961
1048
  console.log(`MCP Server listening on port ${port}`);
962
1049
  });
963
1050
  }
964
- function createServerInstance() {
1051
+ async function createServerInstance() {
965
1052
  // Create a new server instance (you might want to extract server creation logic)
966
1053
  const newServer = new McpServer({
967
1054
  name: "mcp-openmsx",
968
1055
  version: PACKAGE_VERSION,
969
1056
  });
970
1057
  // Re-register all tools (you might want to extract this to a separate function)
971
- registerAllTools(newServer);
1058
+ await registerAllTools(newServer);
972
1059
  return newServer;
973
1060
  }
974
1061
  // ============================================================================
@@ -1012,7 +1099,7 @@ async function main() {
1012
1099
  else {
1013
1100
  // Default to stdio
1014
1101
  const transport = new StdioServerTransport();
1015
- await createServerInstance().connect(transport);
1102
+ (await createServerInstance()).connect(transport);
1016
1103
  }
1017
1104
  }
1018
1105
  main().catch((error) => {
package/dist/utils.js CHANGED
@@ -104,3 +104,20 @@ export function encodeTypeText(text) {
104
104
  export function isErrorResponse(response) {
105
105
  return response.startsWith('Error:') || response.startsWith('error:');
106
106
  }
107
+ /**
108
+ * Get the content of a response, formatting it as a CallToolResult
109
+ * @param response - Array of strings representing the response lines
110
+ * @param isError - Optional boolean indicating if the response is an error
111
+ * @returns CallToolResult - Formatted response content
112
+ */
113
+ export function getResponseContent(response, isError = false) {
114
+ // Check if any response line starts with "Error:" to automatically set isError to true
115
+ const hasError = isError || response.some(line => isErrorResponse(line));
116
+ return {
117
+ content: response.map(line => ({
118
+ type: "text",
119
+ text: line == '' ? "Ok" : line,
120
+ })),
121
+ isError: hasError
122
+ };
123
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nataliapc/mcp-openmsx",
3
- "version": "1.1.5",
3
+ "version": "1.1.8",
4
4
  "description": "Model context protocol server for openMSX automation and control",
5
5
  "main": "dist/server.js",
6
6
  "type": "module",
@@ -45,12 +45,15 @@
45
45
  "zod": "^3.24.4"
46
46
  },
47
47
  "devDependencies": {
48
+ "@modelcontextprotocol/inspector": "^0.15.0",
49
+ "@types/mime-types": "^3.0.1",
48
50
  "@types/node": "^22.15.27",
49
51
  "shx": "^0.4.0",
50
52
  "typescript": "^5.8.3"
51
53
  },
52
54
  "files": [
53
55
  "dist/**/*",
56
+ "resources/**/*",
54
57
  "README.md",
55
58
  "LICENSE"
56
59
  ],
@@ -0,0 +1,31 @@
1
+ {
2
+ "title": "MSX Technical Audio info for MSX",
3
+ "description": "MSX Technical Audio info for MSX",
4
+ "toc": [
5
+ {
6
+ "title": "PSG Registers",
7
+ "uri": "https://www.msx.org/wiki/PSG_Registers",
8
+ "description": "Complete reference for MSX PSG (Programmable Sound Generator) registers covering all 16 registers including frequency control, white noise control, voice and I/O port control, amplitude and volume control, envelope form and period control, and I/O parallel port registers. Includes BIOS routines WRTPSG, RDPSG, and GICINI for PSG access and initialization."
9
+ },
10
+ {
11
+ "title": "Konami SCC Sound Chip",
12
+ "uri": "http://bifi.msxnet.org/msxnet/tech/scc",
13
+ "description": "Technical documentation for Konami's SCC (Sound Creative Chip) used in MSX cartridges. The SCC provides 5-channel wavetable synthesis with custom waveforms, offering enhanced audio capabilities beyond the standard PSG. Commonly found in Konami game cartridges like Gradius series, providing richer sound effects and music."
14
+ },
15
+ {
16
+ "title": "Konami SCC+ Sound Chip",
17
+ "uri": "http://bifi.msxnet.org/msxnet/tech/soundcartridge",
18
+ "description": "Technical documentation for Konami's enhanced SCC+ (Sound Creative Chip Plus) found in later MSX sound cartridges. Improved version of the original SCC providing enhanced wavetable synthesis capabilities, additional sound channels, and better audio quality. Used in advanced Konami cartridges and dedicated sound cartridges for expanded audio functionality."
19
+ },
20
+ {
21
+ "title": "MSX-MIDI",
22
+ "uri": "https://map.grauw.nl/resources/midi/msx-midi.php",
23
+ "description": "Complete technical documentation for MSX-MIDI interface covering hardware configuration, MIDI data communication using 8251 IC, baud rate generator with 8253/8254 timer IC, I/O port assignments (0E8H, 0E9H), internal vs external cartridge implementations, and BASIC extensions. Available only for MSX turbo R and later systems with 16K ROM containing MIDI functionality."
24
+ },
25
+ {
26
+ "title": "MGSDRV MML 1.1",
27
+ "uri": "https://mus.msx.click/index.php?title=MGSDRV_MML_11",
28
+ "description": "Complete documentation for MGSC MML compiler version 1.11 by Ain (1992-93) that converts MML (Music Macro Language) text files to MGS formatted play data for MGSDRV. Covers control functions, OPLL modes (FM 9 sound/FM 6+rhythm), machine vendor codes (SONY, Panasonic, SANYO), macro definitions, tone definitions, tempo control, and compilation options including real-time playback and debugging features."
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,75 @@
1
+ # Calling the BIOS from MSX-DOS
2
+
3
+ In the MSX-DOS environment there is no special entry for calling the MSX BIOS, however you can simply use an interslot call to reach it.
4
+
5
+ You can use this method to call the BIOS:
6
+ ```
7
+ CALSLT: EQU #001C
8
+ EXPTBL: EQU #FCC1
9
+
10
+ Call\_Slot: ld iy,(EXPTBL-1) ;BIOS slot in iyh
11
+ ld ix,nnnn ;address of BIOS routine
12
+ call CALSLT ;interslot call
13
+ ```
14
+
15
+ ## The SUBROM
16
+
17
+ You can’t call the SUBROM from MSX-DOS as you would normally, the reason for this is that both `EXTROM` and `CALSLT` use the `IX` register to pass parameters. Calling directly to the SUBROM doesn’t work either, because using an interslot call to call the SUBROM is not allowed per the MSX2 standard. The reason for this, according to _Alex Wulms_ in _MCM 48_, is that some DiskROMs couldn’t handle the the SUBROM being in page 0.
18
+
19
+ The official means to call the SUBROM from MSX-DOS is by using the following routine, provided by ASCII:
20
+ ```
21
+ ; CALSUB
22
+ ;
23
+ ; In: IX = address of routine in MSX2 SUBROM
24
+ ; AF, HL, DE, BC = parameters for the routine
25
+ ;
26
+ ; Out: AF, HL, DE, BC = depending on the routine
27
+ ;
28
+ ; Changes: IX, IY, AF', BC', DE', HL'
29
+ ;
30
+ ; Call MSX2 subrom from MSXDOS. Should work with all versions of MSXDOS.
31
+ ;
32
+ ; Notice: NMI hook will be changed. This should pose no problem as NMI is
33
+ ; not supported on the MSX at all.
34
+ ;
35
+ CALSLT: EQU #001C
36
+ NMI: EQU #0066
37
+ EXTROM: EQU #015f
38
+ EXPTBL: EQU #fcc1
39
+ H\_NMI: EQU #fdd6
40
+ ;
41
+ CALSUB: exx
42
+ ex af,af' ; store all registers
43
+ ld hl,EXTROM
44
+ push hl
45
+ ld hl,#C300
46
+ push hl ; push NOP ; JP EXTROM
47
+ push ix
48
+ ld hl,#21DD
49
+ push hl ; push LD IX,<entry>
50
+ ld hl,#3333
51
+ push hl ; push INC SP; INC SP
52
+ ld hl,0
53
+ add hl,sp ; HL = offset of routine
54
+ ld a,#C3
55
+ ld (H\_NMI),a
56
+ ld (H\_NMI+1),hl ; JP <routine> in NMI hook
57
+ ex af,af'
58
+ exx ; restore all registers
59
+ ld ix,NMI
60
+ ld iy,(EXPTBL-1)
61
+ call CALLSLT ; call NMI-hook via NMI entry in ROMBIOS
62
+ ; NMI-hook will call SUBROM
63
+ exx
64
+ ex af,af' ; store all returned registers
65
+ ld hl,10
66
+ add hl,sp
67
+ ld sp,hl ; remove routine from stack
68
+ ex af,af'
69
+ exx ; restore all returned registers
70
+ ret
71
+ ```
72
+
73
+ ~Grauw
74
+
75
+ © 2025 MSX Assembly Page. MSX is a trademark of MSX Licensing Corporation.