Issue #1

Letter from the Editor

Hello fellow QB Programmers! Welcome to the first issue of the QB Chronicles! I've got some neat articles for your reading pleasure. I've written three articles: one a newbie article on VARSEG and VARPTR, two commands in QB that can be quite scary and useful at the same time. I've also started a series that focuses more on the programming aspect of QB RPGs. This month's article is more tuned in on programming the "frame" for your RPG, the engine, and its a pretty thick one too! I've also included an article on using XMS in QB which I originally submitted to the QB Times, but has a few corrections in it =). Also you can check out a site review I did on GB Games a fantastic site which you should check out if you already haven't.

If you'd like to write articles for publication, review games and/or sites, or be a "news reporter", then please e-mail me and I'll try to get back to you as soon as I can. Any help would be appreciated greatly!

Also if you spot any errors at all in this issue please e-mail me and tell me about it so I can fix it.

Hopefully you'll enjoy this issue and I look forward to hearing from you! See you in the next month or so!

-Fling-master


Project and Site News and Information

This section is where the authors of the QB Chronicles will put information on projects and sites from the QB community. Don't forget to submit your information to us!


GB Games Review

Category: Comments: Score:
Site URL: http://www.gbgames.com N/A
Graphics: Not to many graphics on this sitei It's more of a text only layout. Though he does have a neat logo.... =) 6/10
Content: Some great content. The theme of the site is game reviews and there are about 30. They are all really detailed and show screenshots of the games. Also has a good amount of links. I like how he also puts news from the QB community on the main page too. There's a message board too, so you can post stuff. 19/20
Layout: Good layout. There are two frames on of course has the site navigation bar. Also it's easy to find what you're looking for too. 8/10
Downloads: Not many downloads, but then that's not the point of the site =) 4/5
Overall Score: 38/45

Beginners guide to VARSEG and VARPTR

This article was written by Fling-master

This article will discuss the use of VARSEG and VARPTR in your QBasic programs. It is intended mainly for newbies to QB but also for people who aren't 100% confident with using these commands.

VARSEG and VARPTR, first of all deal with a variables position in your computer's memory. To get a firm grasp of these commands you should know how to use the values returned. So I will discuss segments and offsets! =)

Segments and offsets are a pair of numbers telling the computer where to read and write data to. A segment is a group of 16 bytes in memory. An offset on the other hand is a group of 1 byte. These two numbers are used together to get the address of something in memory. So if I want to get the data at address 156 in memory I would set the segment to 9 and the offset to 12. If I wanted to get the data at address 34 in memory I would set the segment to 2 and the offset to 1. You could just set the offset to 156 in the first example and 34 in the second but we just don't do that, especially if the desired address is a big number 64000! Also the segment is always first e.g. segment:offset.

So now you know what they are. Now what do they have in common with VARSEG and VARPTR you ask? Well VARSEG returns the passed variables segment address as an unsigned integer (a positive number), and VARPTR returns the passed variables offset address also as an unsigned integer. This is a handy thing especially if you're using stuff like BSAVE, BLOAD, POKE, PEEK, and CALL ABSOLUTE.

So let's do a simple example program here, to show you how you'd use it. We're gonna get the segment and offset address of an integer variable in QB:

'This program will print an integer's segment and offset address.
DIM myvariable%				'We're gonna dim in just in case. =)

segment% = VARSEG(myvariable%)          'Get the segment address
offset% = VARPTR(myvariable%)           'Get the offset address

'Display the variable memory location
PRINT "myvariable% is located at "; segment%; ":"; offset%

There you go! Try it to see if it works. This example though really isn't useful. BUT, when you get a variables address you can do other things like use BSAVE to save it to a file. Make sure that when you get a variables address you do whatever it is you are gonna do with it quickly as the address could change!

Let's make an example that actually does something that could turn into something useful. We're gonna make a program that adds two numbers together and saves the result, using BSAVE to a file. This will require the use of VARSEG.

DIM c%					'Just in case =)
INPUT "Enter number 1: ", a%		'Get number 1 to add
INPUT "Enter number 2: ", b%		'with number 2!

c% = a% + b%				'Add them together

'Here we're setting the default data segment used by QB to the c% variable. This way BSAVE
'get the data from there and save it to the file.
DEF SEG = VARSEG(c%)

'Here we're specifying a file and secondly the offset of c%, then we tell BSAVE the length
'to save which, because c% is an integer, happens to be 2.
BSAVE "c:\number.dat", VARPTR(c%), 2
'Unless you know what you're doing, you should always restore the default segment back to the
'original basic segment. Which is what this does.
DEF SEG

PRINT "Result is"; c%; "!"		'Show the user the result of the addition

Go ahead and try it out! You will see that after the program has finished there will be a file called number.dat in your harddrives root directory. Although the file will be saved as binary. To read this file we use BLOAD. Here's an example which will read the number.dat file from before and display the contents:

DIM c%					'Just in case =)

DEF SEG = VARSEG(c%)			'Set the default segment to c%

'Here we use BLOAD and specifiy the file which contains the number, and the offset of c%
BLOAD "c:\number.dat", VARPTR(c%)
DEF SEG					'Restore the default segment

PRINT "The number you got when you added was"; c%; "!"	'Show the result

There! Simple right? Now you should have a good understanding of VARSEG and VARPTR. You can also use this with strings to but instead of using VARPTR use SADD. Now you can use POKE and PEEK, especially since you know what segments and offsets are. Before using POKE or PEEK you should first set the default segment using DEF SEG to the segment address you want to write/read data from, similar to the above examples.

'$DYNAMIC
DEFINT A-Z

DIM myvar				'Dimension just in case

PRINT "myvar is set to 13."
myvar = 13				'Set myvar=13
DEF SEG = VARSEG(myvar)			'Set default segment to myvar
POKE VARPTR(myvar), 27			'Poke a new value, 27, into the offset of myvar
DEF SEG					'Resetore the default segment
PRINT "myvar now equals:"; myvar        'Print the new value of myvar

There ya go. That'll let you put numbers into variables. Now these examples are sorta useless, but they can be expanded and worked with to meet your needs. A good use for POKE and PEEK is with the famous memory segment A000. This of course is the address of the video segment. Since it's in hexadecimal you should precede it in QB with a &H. POKE will let you draw a pixel, and PEEK will let you read a pixel. One last thing, you can't POKE a value of more than 255, if you do you'll get the value of the number you poked minus 255 (This will never be larger than 255 either.)

If you don't understand this then please e-mail me. Have fun!


RPG Creation 101

This article was written by Fling-master

Unlike most RPG tutorials I'm gonna skip the long part of creating a story, and characters, etc. Now just because I'm skipping that doesn't mean it's not important! Before going in to create an RPG you should sit down and think about that, maybe jot down a few ideas you have. Next comes planning the engine for your RPG. Here are some things you should take into account:

These are just some ideas to think about. And hopefully this series will cover them all! (Though don't expect it to show you how to setup every thing mentioned above heheh.) This month I'll show you how to create a pixel-by-pixel scrolling engine, which'll use two map layers which can give you some neat effects, such as bridges, etc. We'll also touch on some map format things. Two things I'm going to leave for you to decide is the method you use to load tiles/tilesets, and the palette. These things are both quite programmer specific and is not really that much of a trouble to set up.

So lets get into making an engine which is basically the shell for the RPG. This engine which I'm going to help you build is by no means carved in stone. Feel free to change things around and modify it to your needs. You might even know of a few ways to speed it up/enchance it in some way! (If you do then e-mail me and I'll put it in a future article.) Also the graphics routines used are not actually real. They are just put in so you know what it should be. The graphics routines should be in assembly. Maybe sometime in the future I'll tell you how to make some custom routines.

So the basic subs we'll want for now is:

'$DYNAMIC
DEFINT A-Z

DECLARE SUB DrawMap ()			'Draws our map
DECLARE SUB LoadMap (FileName$)       	'Loads the map and processes it
DECLARE SUB MoveUp () 			'These next subs will handle moving up, down, left, and right
DECLARE SUB MoveDown ()
DECLARE SUB MoveLeft ()
DECLARE SUB MoveRight ()

Those subs are the ones we'll use for this part of the series. We'll add on to them eventually. So next we need to set up our data types and global variables:

TYPE EngineType				 'This'll hold our players info and some other things
 x               AS INTEGER                    'Player X position
 y               AS INTEGER                    'Player Y ...
 MaxX            AS INTEGER                    'Maximum X position in map
 MaxY            AS INTEGER                    'Maximum Y ...
 Direction       AS INTEGER                    'Current direction
 Animate         AS INTEGER                    'Animation frame tracker
 Action          AS INTEGER                    'Our action tracker
 MaxNPCs         AS INTEGER                    'Maximum amount of NPCs
END TYPE

TYPE MapType				       'This'll hold the map data.
 tile            AS INTEGER                    'Tile number in map
 tile2           AS INTEGER                    'Tile number for layer 2
 Collision       AS INTEGER                    'Collision data in map
END TYPE

CONST South = 1, North = 2, West = 3, East = 4 'Constant values for directions
CONST True = -1, False = 0                     'True & false (duh!)
DIM SHARED Engine AS EngineType                'Main engine variable
DIM SHARED map(0 TO 63, 0 TO 63) AS MapType    'Map data.

'Here we're gonna dimension our tileset arrays and our offscreen video buffer.
DIM SHARED tilearray(1, 1), PlayerGFX(1, 1), buffer(31999)

These are the data structures we'll be using for right now. Notice we're gonna use a 64x64 map. If we were using XMS to store the map data we could have a much larger map. Some things like Engine.MaxNPCs we'll leave for now. So now we need to load in the tiles, palettes, maps, and setup the screen mode to SCREEN 13:

'320x200x256 resolution
SCREEN 13
DEF SEG = &HA000                      'Video segment is default segment

LoadPalette "yourpal.pal"             'Load our palette
LoadTiles "tiles.dat", tilearray()    'Load the tiles
LoadTiles "npcs.dat", PlayerGFX()     'Load the NPCs
LoadMap "yourmap.map"                 'Load the map

The routines to load the palette and tiles are not real, they are just shown so you know where to put the call to your routines. You will notice that when I load the tiles it is actually loading a tileset. So now we're ready to draw our first view and let the player move around the map. How are we gonna do that? Well we'll create a loop that'll contiually check if a key is being pressed and we'll update the screen no matter if a key is being pressed or not. This allows us to check the FPS and it'll be easier when we implement NPC's and other things like animations. So here's out main loop:

DrawMap						'Draw the map
VideoCopy VARSEG(buffer(0)), VARPTR(buffer(0))  'Copy it to video memory

DO						'Start the loop
 k = INP(&H60)					'Read keyboard status. Faster than INKEY$
 SELECT CASE k					'Now we're gonna find out what key was pressed
  CASE 72					'The up arrow key
	MoveUp					'Move player up
	Engine.Action = True
  CASE 80					'The down arrow key
	MoveDown				'Move player down
	Engine.Action = True
  CASE 75					'The left arrow key
	MoveLeft				'Move player left
	Engine.Action = True
  CASE 77					'The right arrow key
	MoveRight				'Move player right
	Engine.Action = True
  CASE 1					'The ESC key
	END					'Stop the program
 END SELECT
 IF Engine.Action = True THEN			'If the user moved we'll call the script interpreter
  Engine.Action = False				'The script interpreter will be put in later =)
 END IF

 DrawMap					'Update the screen
 RPGVideoCopy VARSEG(buffer(0)), VARPTR(buffer(0))

 fps = fps + 1					'Update FPS counter
 IF starttime& + 1 < TIMER THEN			'If one second has elapsed then we'll show the FPS
  fps2 = fps
  fps = 0					'Set it back to 0
  starttime& = TIMER
 END IF
 LOCATE 1, 1: PRINT fps2			'Display the frame rate
LOOP

Notice that we have put in a FPS counter. This should be removed when we actually make a game. But we'll leave it in for now. I think that was pretty self-explanatory especially with the comments. The code itself is not to hard too understand I think.

Now then, we want to add the MoveXXXX routines to let the player stretch his legs a bit right? Now this is pretty simple in itself. I think we'll make a pixel-by-pixel scroller with Zelda-style free movement! This is actually pretty easy to do excluding the collision detection. Our collision detection is gonna be "map-grid-perfect" which will make it hard to walk through areas with small openings all around. This is easily fixed once we have a multiple keypress handler. So I'm gonna show you the MoveXXXX routines now. Read the comments if you don't understand it:

SUB MoveDown
Engine.Direction = South			'We're moving south.

'What we want to do is get the collision status of two parts of the tile below us. Since our engine deals
'with the actual x and y values not the maps x-grid and y-grid values we need to divide by the tilesize
'which happens to be 16 for this tutorial =)
tile = map(INT(Engine.x \ 16), INT((Engine.y + 16) \ 16)).Collision
tile2 = map(INT((Engine.x + 15) \ 16), INT((Engine.y + 16) \ 16)).Collision
IF tile <> 2 AND tile2 <> 2 THEN		'If the tile is walkable then move the player
 Engine.y = Engine.y + 1			'Set to something different for faster movement
END IF
END SUB

SUB MoveLeft
Engine.Direction = West				'We're moving west

'What we want to do is get the collision status of two parts of the tile to the west of us. Since our engine
'deals with the actual x and y values not the maps x-grid and y-grid values we need to divide by the tilesize
'which happens to be 16 for this tutorial =)
tile = map(INT((Engine.x - 1) \ 16), INT((Engine.y) \ 16)).Collision
tile2 = map(INT((Engine.x - 1) \ 16), INT((Engine.y + 15) \ 16)).Collision
IF tile <> 2 AND tile2 <> 2 THEN		'If the tile is walkable then move the player
 Engine.x = Engine.x - 1			'Set to something different for faster movement
END IF
END SUB

SUB MoveRight
Engine.Direction = East

'What we want to do is get the collision status of two parts of the tile to the east of us. Since our engine
'deals with the actual x and y values not the maps x-grid and y-grid values we need to divide by the tilesize
'which happens to be 16 for this tutorial =)
tile = map(INT((Engine.x + 16) \ 16), INT((Engine.y) \ 16)).Collision
tile2 = map(INT((Engine.x + 16) \ 16), INT((Engine.y + 15) \ 16)).Collision
IF tile <> 2 AND tile2 <> 2 THEN		'If the tile is walkable then move the player
 Engine.x = Engine.x + 1			'Set to something different for faster movement
END IF
END SUB

SUB MoveUp
Engine.Direction = North

'What we want to do is get the collision status of two parts of the tile to above us. Since our engine
'deals with the actual x and y values not the maps x-grid and y-grid values we need to divide by the tilesize
'which happens to be 16 for this tutorial =)
tile = map(INT(Engine.x \ 16), INT((Engine.y - 1) \ 16)).Collision
tile2 = map(INT((Engine.x + 15) \ 16), INT((Engine.y - 1) \ 16)).Collision
IF tile <> 2 AND tile2 <> 2 THEN		'If the tile is walkable then move the player
 Engine.y = Engine.y - 1			'Set to something different for faster movement
END IF
END SUB

Hopefully that isn't to hard to understand. =) Read the comments, they explain it all. Now for the drawing of the map screen. We're gonna have a camera in our RPG which can also be used for cutscenes. Here's the code:

SUB DrawMap
CameraX = Engine.x - 160          'Get the upper left map position on the
CameraY = Engine.y - 96           'the screen.
xtile = INT(CameraX \ 16)         'The the actual map gird position.
ytile = INT(CameraY \ 16)
IF CameraX < 0 THEN CameraX = 0   'If the Camera pos is less then 0 make it 0
IF CameraY < 0 THEN CameraY = 0
'If the Camera position is greater then the screen boundaries make it equal
'the screen boundaries
IF CameraX > Engine.MaxX - 320 THEN CameraX = Engine.MaxX - 320
IF CameraY > Engine.MaxY - 200 THEN CameraY = Engine.MaxY - 200

FOR x = 0 TO 21                             '22 Columns
 FOR y = 0 TO 13                           '14 Rows
  tile = map(x + xtile, y + ytile).tile    'Get layer 1 tile from map buffer
  tile2 = map(x + xtile, y + ytile).tile2  'layer 2...
  Xpos = CameraX MOD 16                    'Get offset into the screen to
  Ypos = CameraY MOD 16                    'draw at.

  'Now draw layer 1 which is not drawn with transparency
  PutSolid VARSEG(buffer(0)), (x * 16) - Xpos, (y * 16) - Ypos, tilearray(0, tile)

  'Now we're gonna draw the second layer
  IF tile2 <> BlankTileNum THEN      'We don't want to draw a blank tile
	'If the tile isn't blank then draw the second layer tile with transparency
	Put VARSEG(buffer(0)), (x * 16) - Xpos, (y * 16) - Ypos, tilearray(0, tile2)
  END IF
 NEXT y
NEXT x

'Here is where you draw the NPC's including the player.
Xpos = Engine.x - CameraX                     'Now get the player's coords
Ypos = Engine.y - CameraY                     'to draw at on screen
PlayerPic = ((Engine.Direction * 2) - 1)      'Get frame to draw player at
Put VARSEG(buffer(0)), Xpos, Ypos, PlayerGFX(0, PlayerPic)  'Draw it
END SUB

Well that will draw two layers onto the off screen buffer. The routine which copies the buffer to video memory should be an asm routine. For right now to see it work you can do this: change all the VARSEG(buffer(0)) to &HA000, then it'll draw the sprites to video memory. But also you'll notice the put routine used is also from a library! Well, I didn't want to get too library specific so I made up some hypothetical graphics routines. Use DirectQB or Future.Library or something for now...

Lastly I'll show you a map loading routine I use. The map data is stored like this:

Tileset file
Palette file
Starting position
Maximum columns
Maximum rows
Map data (layer 1 tile number, layer 2 tile number, collision number)

It's pretty simple. Throughout this series we'll modify this to accomodate NPC's and other things. Here's my map loading routine:

SUB LoadMap (FileName$)
IF INSTR(FileName$, ".") = 0 THEN     'Add a .MAP extension if there
 FileName$ = FileName$ + ".MAP"       'isn't one
END IF
OPEN UCASE$(FileName$) FOR INPUT AS #1   'Open it
INPUT #1, ImageFile$                     'Read tileset file
INPUT #1, PalName$                       'Palette...
INPUT #1, StartX, StartY                 'Starting coords
INPUT #1, Engine.MaxX, Engine.MaxY       'Map size

'Set maximum row and column values to 1 less, since the map buffer starts at 0 not 1
Engine.MaxY = Engine.MaxY - 1
Engine.MaxX = Engine.MaxX - 1

'Load map data into 2 layers
FOR i = 0 TO Engine.MaxY
 FOR J = 0 TO Engine.MaxX
  INPUT #1, map(J, i).tile, map(J, i).tile2, map(J, i).Collision
 NEXT J
NEXT i

'Calculate the map size in pixels.
Engine.MaxX = Engine.MaxX * 16
Engine.MaxY = Engine.MaxY * 16
CLOSE #1

END SUB

The map data could also have been stored in binary too, but regardless of format type, it works. Notice that you don't have to specify an extension on the filename if it ends in .map =) Well that's it for this part of the tutorial and I'd have to say it's pretty big! Next time I'll cover stuff like NPC's and animation. I also might include a demo program. If you don't understand something here then please e-mail me (address is at the top).


Using XMS in QB

This article was written by Fling-master

This tutorial will give you a start to programming routines for the Extended Memory Specification, or XMS. Please note that this is for advanced QB programmers only! I also expect you know some basic assembly. If not than I would suggest not reading this. Don't say I didn't warn you! =) Now that that's out of the way, I can begin.

Is your game, utility, or whatever running out of memory? Do you get all red in the face when QuickBASIC stops your program and displays the "Out of memory" error? If so then XMS is for you. XMS gives you huge amounts of memory which is limited by your computers RAM. This will usually be enough for most QB programs. "Great," you say "So how do I use it then?" Well first you need to find out if your computer has XMS. (I feel sorry for you if it doesn't.) This is determined via interrupt 2Fh. The following example routine will check if it exists, initialize it for use, AND return the version installed:

XMSInit PROC
 mov ax,4300h                        ;This function lets us detect XMS
 int 2Fh                             ;Use interrupt 2Fh.
 cmp al,80h                          ;If AL isn't 80h then XMS is not present!
 jne NoXMS
 mov ax,4310h                        ;Since XMS is present lets get the version number
 int 2Fh                             ;and the address of the control function.
 mov word ptr [XMSControl],bx        ;XMSControl must be a DWORD.
 mov word ptr [XMSControl+2],es
 xor ax,ax                           ;Now run function 00h which returns the version.
 call [XMSControl]                   ;Call the XMS Control function.
 ret
NoXMS:
 mov ax,-1                           ;If there's no XMS driver we return a -1 to QB.
 ret
ENDP XMSInit

If you found that confusing the read the comments carefully. There's a funny thing about the version number returned. If you try to print it, it won't be the right number. Use this code to fix that:

XMSversion$ = str$(version% / &H100)+"."+str$(version% AND &HFF)

That will return the version as a string. version% would be the number returned by the above function. If you don't have version 3.0 or later than these functions I'm showing you might not work.

Now wouldn't you like to know how much memory is available to you? Well there's a function for that. Function 08h. It returns the largest free block in kb into AX, and the total free memory in kb into DX. Here's an example routine:

XMSInfo PROC freemem:WORD, totalmem:WORD
 mov ah,08h                          ;Use function 08h
 call [XMSControl]                   ;Call the API
 mov bx,totalmem                     ;Put the value in DX in totalmem.
 mov [bx],dx 
 mov bx,freemem                      ;Put the value of AX in freemem
 mov [bx],ax
 ret
ENDP XMSInfo

That will return the available memory in freemem and the total memory in totalmem. One thing about the above routine, it won't show an amount greater than 64MB. There is a function which will but I'm not showing you it now. I want you first to understand how it all works. Next issue you'll see. =P

Ok now, to actually use XMS you have to allocate a block of it. That is, you have to tell the computer that you want x amount of XMS for use in your program. We can do that via function 09h. With this routine it is not possible to allocate over 64MB or memory. There is a routine for it but I'll show you it next issue. Before you call the routine you have to have the amount of kb you want to allocate in DX. After you call it AX will be 0001h if it was successful. If not 0000h is returned. DX will also return the 16-bit handle of the allocated block. "Handle? What the heck!?" The handle you get is your "password" to accessing the block of memory. So when you allocate memory make sure that you keep the handle! There is no way to get it back and worse, you can't deallocate the block without it! Then other programs won't be able to use that block until you restart your computer! So before I get ahead of myself, here's an example routine to allocate a block:

XMSAlloc PROC kb:WORD
 mov ah,09h                         ;Use function 09h
 mov dx,kb                          ;Move kb into DX.
 call [XMSControl]                  ;Call the API
 cmp ax,0001h                       ;Is AX = 0001h? If it is then it worked!
 jne AllocErr                       ;If it isn't then . . . too bad! =P
 mov ax,dx                          ;Move the handle into AX so its returned.
 ret
AllocErr:                           ;Since we can't get a handle if it didn't work we don't
 ret                                ;want DX to get into AX.
ENDP XMSAlloc

There ya go! That will allocate up to 64MB. So lets find out how to deallocate a block of memory.

Deallocating isn't that hard. It basically works the same way. We need to make use of function 0Ah for it. DX must hold the handle of the allocated block to be freed up. After called AX will have 0001h in it if successful. If not the its 0000h. Here's the code:

XMSDeAlloc PROC handle:WORD
 mov ah,0Ah                         ;Use function 0Ah
 mov bx,handle                      ;Put the handle into DX
 mov dx,[bx]
 call [XMSControl]                  ;Call the API
 ret
ENDP XMSDeAlloc

Pretty simple ain't it? Now if used as a function it'll return 0001h if successful.

Now you've probably asked yourself: "How do I know what error occurs?" Well every XMS function will return an error code into BL if an error happened. There's quite a few error codes so I'll put 'em in a nice table for you all to see!

80h Function not implemented 92h DX less than /HMAMIN= parameter A4h SOffset is invalid ABh Block is locked
81h VDISK device detected 93h HMA not allocated A5h DHandle is invalid ACh Block lock count overflow
82h A20 error occured 94h A20 line still enabled A6h DOffset is invalid ADh Lock failed
8Eh General driver error occured A0h All XMS allocated A7h Len is invalid B0h Smaller UMB is available
8Fh Unrecoverable driver error occured A1h All available XMS handles in use A8h Move has an invalid overlap B1h No UMBs are available
90h HMA does not exist A2h Handle is invalid A9h A parity error occurs B2h UMB segment number is invalid
91h HMA already in use A3h SHandle is invalid AAh Block is not locked

Some of those errors won't apply here so you can ignore them. (Like the ones dealing with the A20 line.) What you could do is make the routine check if an error occured and then it could return that to QB. Although you don't have to.

Alright, now comes moving between XMS and QB. To do this you have to define a data structure at the beginning of your assembly source file like the one below:

MoveXMS struc		            ;Our main XMS data structure
  Len		dd ?		    ;number of bytes to transfer
  SHandle	dw ?		    ;handle of the source block
  SOffset	dd ?		    ;offset into the source block
  DHandle	dw ?		    ;handle of the destination block
  DOffset	dd ?		    ;offset into the destination block
MoveXMS ends

XMSmove MoveXMS ?                   ;This makes it so we can use it =)

Using this structure we'll be able to specify what variable you want to move from QB to XMS and vice-versa. The best way to explain this I think is with an example. This sample routine will let you move data from QB to XMS:

XMSPut proc handle:WORD, segm:WORD, offs:WORD, bytes:DWORD, XMSoffs:DWORD
 mov eax,bytes			   ;Get the length of the data to move into Len
 mov XMSmove.Len,eax
 mov XMSmove.SHandle,0000h         ;SHandle must be set to 0000h to move TO XMS.
 mov bx,offs                       ;Now we move the offset of the variable to SOffset.
 mov word ptr [XMSmove.SOffset],bx
 mov bx,segm                       ;Then the segment goes to SOffset+2
 mov word ptr [XMSmove.SOffset+2],bx
 mov bx,handle                     ;Now we must move the destination handle. This is the
 mov XMSmove.DHandle,bx            ;handle which we're moving the data to.
 mov eax,XMSoffs                   ;This is the location in the specified handle to move
 mov XMSmove.DOffset,eax           ;the data to.
 mov si,Offset XMSmove             ;Now we get the offset address of the data structure into SI
 mov ah,0bh                        ;And make sure that we call function 0Bh
 call [XMSControl]                 ;Now call the XMS driver to move the data.
 ret
endp XMSPut

There. That will correctly move data from a variable, array, whatever in QB to the specified XMS handle. DON'T use this function unless you've allocated a block! If the move was successful AX will return 0001h or else it returns 0000h. I would make this routine a function so that you can tell from QB if it was successful or not. If that was too complicated for you than just go over it again slowly until you get it. Now, how do we move data from XMS to a variable in QB? Well we really only have to change a little bit of the above code:

XMSGet proc handle:WORD, segm:WORD, offs:WORD, bytes:DWORD, XMSoffs:DWORD
 mov eax,bytes                     ;Get the length to move into Len.
 mov XMSmove.Len,eax
 mov bx,handle                     ;Get the handle of the XMS block with the wanted data into
 mov XMSmove.SHandle,bx            ;SHandle as XMS is now the source.
 mov eax,XMSoffs                   ;Also get the offset into the handle into SOffset.
 mov XMSmove.SOffset,eax
 mov XMSmove.DHandle,0000h         ;DHandle must be 0000h so that we can pass the segment and
                                   ;offset of a QB variable to DOffset.
 mov bx,offs                       ;Get the offset of the QB variable.
 mov word ptr [XMSmove.DOffset],bx
 mov bx,segm                       ;And the segment.
 mov word ptr [XMSmove.DOffset+2],bx
 mov si, Offset XMSmove            ;Now get the offset of the data structure into SI.
 mov ah,0bh                        ;Use function 0Bh
 call [XMSControl]                 ;Call the API!
 ret
endp XMSGet

That will return the same as the other function into AX depending on what happened. As you probably saw we just used function 0Bh like before just we switched some things around. Not that hard, eh? Now all that's left for me to show you this issue is how to Reallocate a XMS block. This is handy if you need more memory for your game or something, I really don't know =). To reallocate a block you call subfunction 0Fh. Heres the commented code:

XMSReAlloc proc handle:WORD, kb:WORD
 mov ah,0Fh                        ;Use subfunction 0Fh
 mov bx,handle                     ;Get the handle to deallocate into DX.
 mov dx,[bx]
 mov bx,kb                         ;Get the new amount to resize the handle to into BX.
 call [XMSControl]                 ;Call the API.
 mov bx,handle                     ;Return the handle to QB.
 mov [bx],ax
endp XMSReAlloc

As you can see it's pretty similar to the allocation routine. This like the allocation routine will only allocate up to 64MB. There is a routine that'll allow more (up to 4GB) but I want you to understand these first before you dive in. And thats all I have to say for this issue. Next issue I'll show you the other allocation routines and we'll combine this into a lib you can use. If you still don't understand any of this than don't hesitate to e-mail me!


Credits

Only me Fling-master, the editor, this month =)

Next Issue

Well that's it for this issue! I hope to hear from you on what you thought about this issue here. I'll put a few responses provided I get any, in issue #2. Next time you can expect part two of RPG Creation 101, part two of the XMS article, and maybe a tutorial on using the palette, among other articles. The next issue should be out in a month or so. See ya then!

All site content is © Copyright 2001, HyperRealistic Games. This excludes content submitted by other people.