Chit chat

=======

If there is something in particular you are looking for please use the search function on the upper right hand side of the page.

Screenshot - 01092015 - 08:17:30 PM

Interesting that we were looking at an old BSD box. Started to use some of the package commands. Surprised it had nano and not vim. After setting the package path, we were also able to add vim easily. Unfortunately, the install is several versions behind. Since nothing was ever really installed on the machine, it might be easier to reinstall from scratch. Shut the system down for the moment.

Plugged in another drive into that same system and found Tinycore linux including gui installed on it  Amazing it runs with only a P1 and a legacy isa not “pci vga card. You sure could not do that with the latest version of the commercial operating systems. Did a tce-update and kept on rolling.

Had another old motherboard go south. With the bulging capacitors. Shame though it worked well in an atx case. Probably will not take the time to repair it. Will scavenge parts like the battery holder and et cetera for electronics projects.


Was experimenting what to do with some old Realtek network interface cards (aka nics) that there was a patch available for allowing them to be pxebootable.

ls:

$ ls
1 2 3 4 5
$ ls -l
total 0
-rw-rw-r– 1 eddie eddie 0 Jan 10 05:13 1
-rw-rw-r– 1 eddie eddie 0 Jan 10 05:13 2
-rw-rw-r– 1 eddie eddie 0 Jan 10 05:13 3
-rw-rw-r– 1 eddie eddie 0 Jan 10 05:13 4
-rw-rw-r– 1 eddie eddie 0 Jan 10 05:13 5
x$ ls -rl
total 0
-rw-rw-r– 1 eddie eddie 0 Jan 10 05:13 5
-rw-rw-r– 1 eddie eddie 0 Jan 10 05:13 4
-rw-rw-r– 1 eddie eddie 0 Jan 10 05:13 3
-rw-rw-r– 1 eddie eddie 0 Jan 10 05:13 2
-rw-rw-r– 1 eddie eddie 0 Jan 10 05:13 1
$ ls -rt
1 2 3 4 5
$ ls -t
5 4 3 2 1

Watch nic input:

$ watch -n1 “ifconfig eth0 | grep ‘RX'”

Screenshot - 01112015 - 03:42:51 PM

Installed freebasic on the old P1 with Slackware. Copied a couple of files over and they seemed to compile and run okay.

Had to change the nic on the Slackware setup, Wanted to use a cheaper card.  Found some docs on the net and fixed it quickly. Updated the router for the  mac change of the IPaddress reservation.

—————————————

Text clocks seem to be a big thing right now. Here is one written in qbasic.

qb64 result:

Freebasic result;

Source code:

'UNSEEN'S NOT A STANDARD CLOCK
DIM SEC AS STRING, MIN AS STRING, HR AS STRING
SCREEN 0: COLOR 3, 15: WIDTH 40, 25
CLS
DO
HR$ = LEFT$(TIME$, 2): MIN$ = MID$(TIME$, 4, 2): SEC$ = RIGHT$(TIME$, 2)
LOCATE 1, 1: PRINT HR$; ":"; MIN$; ":"; SEC$
'MINUTES
IF (VAL(MIN$) <  10 and val(min$) >= 5) OR (VAL(MIN$) >= 55 AND VAL(MIN$) < 60) THEN COLOR 12, 15
LOCATE 3, 2: PRINT "FIVE": COLOR 3, 15
IF VAL(MIN$) >= 10 AND VAL(MIN$) < 15 OR VAL(MIN$) >= 50 AND VAL(MIN$) < 55 THEN COLOR 12, 15
LOCATE 3, 8: PRINT "TEN": COLOR 3, 15
IF VAL(MIN$) >= 15 AND VAL(MIN$) < 20 OR VAL(MIN$) >= 45 AND VAL(MIN$) < 50 THEN COLOR 12, 15
LOCATE 3, 13: PRINT "FIFTEEN": COLOR 3, 15
IF VAL(MIN$) >= 20 AND VAL(MIN$) < 25 OR VAL(MIN$) >= 40 AND VAL(MIN$) < 45 THEN COLOR 12, 15
LOCATE 3, 21: PRINT "TWENTY": COLOR 3, 15
IF VAL(MIN$) >= 25 AND VAL(MIN$) < 30 OR VAL(MIN$) >= 35 AND VAL(MIN$) < 40 THEN COLOR 12, 15
LOCATE 3, 28: PRINT "TWENTY FIVE": COLOR 3, 15
LOCATE 7, 12: PRINT "MIN'S"
'EXTRA MINUTES
IF VAL(MIN$) > 30 THEN COLOR 12, 15
LOCATE 5, 3: PRINT "MINUS": COLOR 3, 15
IF VAL(MIN$) < 30 THEN COLOR 12, 15
LOCATE 5, 10: PRINT "PLUS": COLOR 3, 15
IF VAL(MID$(TIME$, 5, 1)) = 6 OR VAL(MID$(TIME$, 5, 1)) = 4 THEN COLOR 12, 15
LOCATE 7, 3: PRINT "1": COLOR 3, 15
IF VAL(MID$(TIME$, 5, 1)) = 7 OR VAL(MID$(TIME$, 5, 1)) = 3 THEN COLOR 12, 15
LOCATE 7, 5: PRINT "2": COLOR 3, 15
IF VAL(MID$(TIME$, 5, 1)) = 8 OR VAL(MID$(TIME$, 5, 1)) = 2 THEN COLOR 12, 15
LOCATE 7, 7: PRINT "3": COLOR 3, 15
IF VAL(MID$(TIME$, 5, 1)) = 9 OR VAL(MID$(TIME$, 5, 1)) = 1 THEN COLOR 12, 15
LOCATE 7, 9: PRINT "4": COLOR 3, 15
'TO PAST
IF VAL(MIN$) > 30 THEN COLOR 12, 15
LOCATE 9, 10: PRINT "TO THE NEXT HOUR": COLOR 3, 15
IF VAL(MIN$) <= 30 THEN COLOR 12, 15
LOCATE 9, 30: PRINT "PAST": : COLOR 3, 15
IF VAL(MIN$) = 30 THEN COLOR 12, 15
LOCATE 9, 36: PRINT "HALF ": COLOR 3, 15
'HOURS
IF VAL(HR$) = 1 OR VAL(HR$) = 13 THEN COLOR 12, 15
LOCATE 11, 2: PRINT "ONE": COLOR 3, 15
IF VAL(HR$) = 2 OR VAL(HR$) = 14 THEN COLOR 12, 15
LOCATE 11, 7: PRINT "TWO": COLOR 3, 15
IF VAL(HR$) = 3 OR VAL(HR$) = 15 THEN COLOR 12, 15
LOCATE 11, 12: PRINT "THREE": COLOR 3, 15
IF VAL(HR$) = 4 OR VAL(HR$) = 16 THEN COLOR 12, 15
LOCATE 11, 19: PRINT "FOUR": COLOR 3, 15
IF VAL(HR$) = 5 OR VAL(HR$) = 17 THEN COLOR 12, 15
LOCATE 11, 25: PRINT "FIVE": COLOR 3, 15
IF VAL(HR$) = 6 OR VAL(HR$) = 18 THEN COLOR 12, 15
LOCATE 11, 30: PRINT "SIX": COLOR 3, 15
IF VAL(HR$) = 7 OR VAL(HR$) = 19 THEN COLOR 12, 15
LOCATE 11, 35: PRINT "SEVEN": COLOR 3, 15
IF VAL(HR$) = 8 OR VAL(HR$) = 20 THEN COLOR 12, 15
LOCATE 12, 5: PRINT "EIGHT": COLOR 3, 15
IF VAL(HR$) = 9 OR VAL(HR$) = 21 THEN COLOR 12, 15
LOCATE 12, 12: PRINT "NINE": COLOR 3, 15
IF VAL(HR$) = 10 OR VAL(HR$) = 22 THEN COLOR 12, 15
LOCATE 12, 18: PRINT "TEN": COLOR 3, 15
IF VAL(HR$) = 11 OR VAL(HR$) = 23 THEN COLOR 12, 15
LOCATE 12, 23: PRINT "ELEVEN": COLOR 3, 15
IF VAL(HR$) = 0 OR VAL(HR$) = 12 THEN COLOR 12, 15
LOCATE 12, 31: PRINT "TWELVE": COLOR 3, 15
'AM-PM
IF VAL(HR$) >= 12 THEN COLOR 12, 15
LOCATE 16, 5: PRINT "PM": COLOR 3, 15
IF VAL(HR$) < 12 THEN COLOR 12, 15
LOCATE 16, 2: PRINT "AM": COLOR 3, 15
'SECONDS
COLOR 12, 15: LOCATE 19, 2: PRINT "AND"
COLOR 3, 15
IF VAL(SEC$) = 5 THEN COLOR 12, 15
LOCATE 21, 1: PRINT "5": COLOR 3, 15
IF VAL(SEC$) = 10 THEN COLOR 12, 15
LOCATE 21, 3: PRINT "10": COLOR 3, 15
IF VAL(SEC$) = 15 THEN COLOR 12, 15
LOCATE 21, 7: PRINT "15": COLOR 3, 15
IF VAL(SEC$) = 20 THEN COLOR 12, 15
LOCATE 21, 11: PRINT "20": COLOR 3, 15
IF VAL(SEC$) = 25 THEN COLOR 12, 15
LOCATE 21, 15: PRINT "25": COLOR 3, 15
IF VAL(SEC$) = 30 THEN COLOR 12, 15
LOCATE 21, 19: PRINT "30": COLOR 3, 15
IF VAL(SEC$) = 35 THEN COLOR 12, 15
LOCATE 21, 23: PRINT "35": COLOR 3, 15
IF VAL(SEC$) = 40 THEN COLOR 12, 15
LOCATE 21, 27: PRINT "40": COLOR 3, 15
IF VAL(SEC$) = 45 THEN COLOR 12, 15
LOCATE 21, 31: PRINT "45": COLOR 3, 15
IF VAL(SEC$) = 50 THEN COLOR 12, 15
LOCATE 21, 35: PRINT "50": COLOR 3, 15
IF VAL(SEC$) = 55 THEN COLOR 12, 15
LOCATE 21, 39: PRINT "55": COLOR 3, 15
COLOR 12, 15: LOCATE 23, 32: PRINT "SECONDS"
COLOR 3, 15
LOOP

—————————————

The Chumby makes a great Musak type device. You will need a program like gnump3d on a server with some audio files.

FVIYRO9I4IDCEC4.LARGE

You can use a program on linux called fapg to create an m3u playlist.Our connection from the Chumby to the server is:

FTB1702I4IDD8C1.LARGE

Server Name: Typo1

URL: http://typo1:8888:/stream.m3u

typo1:/var/media/music

Note: music server directory is set to /var/media/music
typo1:/var/media/music$ ls
bsd contemporary stream.m3u clientplaylist_192.168.1.122.m3u folk and classical

—————————————

Simple mandlebrot prog compile in freebasic.


Screenshot from 2015-01-06 14:51:29

SCREEN 13
WINDOW (-2, 1.5)-(2, -1.5)
FOR x0 = -2 TO 2 STEP .01
FOR y0 = -1.5 TO 1.5 STEP .01
x = 0
y = 0
iteration = 0
maxIteration = 223
WHILE (x * x + y * y <= (2 * 2) AND iteration < maxIteration)
xtemp = x * x - y * y + x0
y = 2 * x * y + y0
x = xtemp
iteration = iteration + 1
WEND
IF iteration <> maxIteration THEN
c = iteration
ELSE
c = 0
END IF
PSET (x0, y0), c + 32
NEXT
NEXT
Input"press enter to quit",a

Qb64 version:

Screenshot from 2015-01-06 14:51:29

—————————————

Screenshot - 01092015 - 10:03:30 PM

One thing that you home router provides is internet protocol addresses to every machine on the network with out you having to manually assign the ipaddresses (aka computer telephone numbers) to all the systems that request an address. It also associates hostnames for system connected to it like a telephone book.  If for some reason your router fails, you can use an existing system to replace those services. You just need to install a software package known as Dnsmasq. In fact. that is the exact software most routers use to be a localize domain name services. You will want to use an extra nic so that later you can turn the system into a router.

From the Debian wiki.

Dnsmasq is a lightweight, easy to configure, DNS forwarder and DHCP server. It is designed to provide DNS and optionally, DHCP, to a small network. It can serve the names of local machines which are not in the global DNS. The DHCP server integrates with the DNS server and allows machines with DHCP-allocated addresses to appear in the DNS with names configured either in each host or in a central configuration file. Dnsmasq supports static and dynamic DHCP leases and BOOTP/TFTP for network booting of diskless machines (source: from the package description).

Basic DNS setup

First things first, let’s install the package:

apt-get update
apt-get install dnsmasq

If your goal was to set up a simple DNS server, you just succeeded. To test it, use your favorite DNS lookup tool pointed at localhost:

dig debian.org @localhost

or

nslookup debian.org localhost

By default, DNS is configured to forward all requests to your system’s default DNS settings. In case you didn’t know, these are stored in the /etc/resolv.conf file. See Debian Reference or the resolv.conf(5) man page for more details.

Now, if you want to add some names for your DNS server to resolve for your clients, simply add them to your /etc/hosts file.

Interfaces

One thing you will probably want to do is tell dnsmasq which ethernet interface it can and cannot listen on, as we really don’t want it listening on the internet. Around line 69 of the /etc/dnsmasq.conf file, you will see:

#interface=

Uncomment the line and specify which ethernet interface(s) you want it server IPs to. For example, if I want it to listen on eth1 (my DMZ) and eth2 (my local network), then it should look like:

interface=eth1
interface=eth2

If I didn’t edit this line, it would also listen on eth0, my internet connection. I personally wouldn’t recommend this, as it gives those evil guys a few doors to try to break into.

Basic dhcp setup

By default, DHCP is turned off. This is a good thing, as you could bring down whatever network you are connected to if you are not careful.

To enable it, there is at least one line will need to edit in the /etc/dnsmasq.conf file. Around line 143, you will see: Make sure the existing network you are plugging into is not 192.168.0.x

#dhcp-range=192.168.0.50,192.168.0.150,12h

To enable the DHCP server, you will need to give it a range of IP addresses to hand out. In the example above, this server would hand out 101 address starting at 192.168.0.50 and ending at 192.168.0.150. The last number is how long the DHCP leases are good for. In this example, they would be good for twelve hours.

(Assuming he is using three nics and you are not using an existing device using dnsmasq) Since I have two different networks that need DHCP, I’m going to change that line to:

dhcp-range=eth1,192.168.100.100,192.168.100.199,4h
dhcp-range=eth2,192.168.200.100,192.168.200.199,4h

Notice the “eth1” and “eth2” labels in the lines above? The aren’t necessary, but definately help once you start playing with more advanced configurations. It also helps me remember which range is which. Now restart your dnsmasq server, connect up a few clients, and see if they autoconfigure themselves:

/etc/init.d/dnsmasq restart

Local caching

Using dnsmasq to cache DNS queries for the local machine is a bit tricky, since all DNS queries from the local machine need to go to dnsmasq, while as the same time, dnsmasq must be configured to forward all those queries to upstream DNS servers.

  • <!> Do not use this configuration if you use different network (e.g If you use a laptop!)

The dnsmasq(8) man page suggests the following:

  • In order to configure dnsmasq to act as cache for the host on which it is running, put “nameserver 127.0.0.1” in /etc/resolv.conf to force local processes to send queries to dnsmasq. Then either specify the upstream servers directly to dnsmasq using –server options or put their addresses real in another file, say /etc/resolv.dnsmasq and run dnsmasq with the -r /etc/resolv.dnsmasq option. This second technique allows for dynamic update of the server addresses by PPP or DHCP.

There is, however, a simpler method; simply ensure that the machine’s list of nameservers contains the line

nameserver 127.0.0.1

as the first line, followed by the upstream nameservers. dnsmasq is smart enough to ignore this line and forward all queries appropriately, while all other applications will send all their queries to dnsmasq.

Exaclty how to do this depends on the method(s) of network configuration in use. If you’re manually hardcoding the nameservers (either in /etc/resolv.conf or elsewhere, such as a stanza in /etc/network/interfaces or in the Wicd GUI), then just add a reference to 127.0.0.1 as the first entry in the list. If you’re using DHCP, then instruct your client to prepend 127.0.0.1 to the DHCP servers it receives. E.g., with dhclient, include the line

prepend domain-name-servers 127.0.0.1;

in the dhclient configuration file (/etc/dhcp3/dhclient.conf). [On my Sid system, the default configuration file shipped with the package contains that line, but commented out.]

Note that if you plan to use dnsmasq for the local system only, you should lock it down by adding the line (not used on out dnsmasq setup when I looked at the file.)

listen-address=127.0.0.1

to the dnsmasq configuration file (/etc/dnsmasq.conf).

—————————————

Screenshot - 01102015 - 12:07:53 AM

Debian gateway/router.

A multitude of reasons exist as to why one would want to build a custom router vs. suffer with the performance, reliability issues, and limitations of an off-the-shelf solution.  What we are about to do is configure an incredibly fast and stable router/gateway solution for your home/office in about 15 minutes. (Note: This post assumes you already have your machine loaded up with a fresh copy of Debian and you have the two needed NICs installed. With systemd on the horizon, this setup will change. I would probably use auto instead of hotplug to configure the interfaces.

First, let’s make three initial assumptions:

  • eth0 is the public interface (the Cable/DSL modem is attached to this NIC)
  • eth1 is the private interface (your switch is connected to this NIC)
  • All of the client computers, servers, WAPs, etc. are connected to the switch

Let’s get started with the configuration. Set your timer and type quickly! 🙂

1.) Configure the network interfaces
Change the “address”, “netmask”, and “broadcast” values to match your internal network preferences.

nano -w /etc/network/interfaces
# The external WAN interface (eth0)
# auto eth0
allow-hotplug eth0
iface eth0 inet dhcp

# The internal LAN interface (eth1)
# auto eth1
allow-hotplug eth1
iface eth1 inet static
   address 192.168.0.1
   netmask 255.255.255.0
   network 192.168.0.0
   broadcast 192.168.0.255

2. Install and configure DNSmasq
DNSmasq is DNS forwarder and DHCP server. Change “domain” to the FQDN of your network and “dhcp-range” to the desired range of DHCP addresses you would like your router to serve out to clients.

apt-get install dnsmasq
nano -w /etc/dnsmasq.conf
interface=eth1
#not used this feature, but it might be a good idea to.
listen-address=127.0.0.1
domain=home.andreimatei.com
dhcp-range=192.168.0.100,192.168.0.110,12h

3.) Enable IP Forwarding
Uncomment the following line:

nano -w /etc/sysctl.conf
net.ipv4.ip_forward=1

4.) Configure iptables
We create a file called /etc/iptables.rules and put this rule set inside of it.  As an example, this set includes allowing tcp traffic in from the outside world on port 222 (I run SSH on this alternate port) and also port-forwards tcp port 50,000 to an internal machine with the ip of 192.168.0.3.  Use this as a guide for your own rules. This known as a firewall script. Use this or any other script at your own risk.

nano -w /etc/iptables.rules
*nat
-A PREROUTING -i eth0 -p tcp -m tcp --dport 50000 -j DNAT --to-destination 192.168.0.3:50000
-A POSTROUTING -o eth0 -j MASQUERADE
COMMIT

*filter
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 222 -j ACCEPT
-A INPUT -i eth0 -j DROP
-A FORWARD -i eth0 -p tcp -m tcp --dport 50000 -m state --state NEW -j ACCEPT
COMMIT

5.) Activate your iptables rules

iptables-restore < /etc/iptables.rules

6.) Ensure iptables rules start on boot
Insert the following line into your /etc/network/interfaces file right underneath “iface lo inet loopback”

nano -w /etc/network/interfaces
pre-up iptables-restore < /etc/iptables.rules

7.) Reboot and Verify
That’s it! After a reboot, you should now have a very basic Linux Router/Gateway for your network.

This post obviously doesn’t cover some of the incredible additional flexibility which your new machine provides.  I urge you to explore topics on traffic shaping, throughput monitoring, Intrusion Detection, and VPN configuration to learn how to harness the true power of running a dedicated machine as the central traffic cop of your network.

Other firewall scripts:

# if you don't have wget on your system, install it (on debian apt-get install wget)
wget http://robert.penz.name/files/firewall/iptables_firewall_scripts-0.3.tar.bz2

tar xjf iptables_firewall_scripts-0.3.tar.bz2
# if you get an error message you don't have the bzip2 installed --> install it
# (on debian apt-get install bzip2)

Also see:

https://help.ubuntu.com/community/IptablesHowTo

http://www.perkin.org.uk/posts/iptables-script-for-debian-ubuntu.html

https://wiki.debian.org/Firewalls

https://wiki.debian.org/iptables

Robert Penz Blog Logo Robert Penz Blog URL

—————————————

Code location: https://github.com/skilldrick/6502js

Introduction

In this tiny ebook I’m going to show you how to get started writing 6502 assembly language. The 6502 processor was massive in the seventies and eighties, powering famous computers like the BBC Micro, Atari 2600, Commodore 64, Apple II, and the Nintendo Entertainment System. Bender in Futurama has a 6502 processor for a brain. Even the Terminator was programmed in 6502.

So, why would you want to learn 6502? It’s a dead language isn’t it? Well, so’s Latin. And they still teach that. Q.E.D.  (Actually, I’ve been reliably informed that 6502 processors are still being produced by Western Design Center, so clearly 6502 isn’t a dead language! Who knew?)

Seriously though, I think it’s valuable to have an understanding of assembly language. Assembly language is the lowest level of abstraction in computers – the point at which the code is still readable. Assembly language translates directly to the bytes that are executed by your computer’s processor. If you understand how it works, you’ve basically become a computer magician.

Then why 6502? Why not a useful assembly language, like x86? Well, I don’t think learning x86 is useful. I don’t think you’ll ever have to write assembly language in your day job – this is purely an academic exercise, something to expand your mind and your thinking. 6502 was originally written in a different age, a time when the majority of developers were writing assembly directly, rather than in these new-fangled high-level programming languages. So, it was designed to be written by humans. More modern assembly languages are meant to written by compilers, so let’s leave it to them. Plus, 6502 is fun. Nobody ever called x86 fun.

Our first program

So, let’s dive in! That thing below is a little JavaScript 6502 assembler and simulator that I adapted for this book. Click Assemble then Run to assemble and run the snippet of assembly language.

LDA #$01
STA $0200
LDA #$05
STA $0201
LDA #$08
STA $0202

Debugger

Monitor Start: $ Length: $

Hopefully the black area on the right now has three coloured “pixels” at the top left. (If this doesn’t work, you’ll probably need to upgrade your browser to something more modern, like Chrome or Firefox.)
So, what’s this program actually doing? Let’s step through it with the debugger. Hit Reset, then check the Debugger checkbox to start the debugger. Click Step once. If you were watching carefully, you’ll have noticed that A= changed from $00 to $01, and PC= changed from $0600 to $0602.

Any numbers prefixed with $ in 6502 assembly language (and by extension, in this book) are in hexadecimal (hex) format. If you’re not familiar with hex numbers, I recommend you read the Wikipedia article. Anything prefixed with # is a literal number value. Any other number refers to a memory location.
Equipped with that knowledge, you should be able to see that the instruction LDA #$01 loads the hex value $01 into register A. I’ll go into more detail on registers in the next section.

Press Step again to execute the second instruction. The top-left pixel of the simulator display should now be white. This simulator uses the memory locations $0200 to $05ff to draw pixels on its display. The values $00 to $0f represent 16 different colours ($00 is black and $01 is white), so storing the value $01 at memory location $0200 draws a white pixel at the top left corner. This is simpler than how an actual computer would output video, but it’ll do for now.

So, the instruction STA $0200 stores the value of the A register to memory location $0200. Click Step four more times to execute the rest of the instructions, keeping an eye on the A register as it changes.

Exercises

  1. Try changing the colour of the three pixels.
  2. Change one of the pixels to draw at the bottom-right corner (memory location $05ff).
  3. Add more instructions to draw extra pixels.

Registers and flags

We’ve already had a little look at the processor status section (the bit with A, PC etc.), but what does it all mean?  The first line shows the A, X and Y registers (A is often called the “accumulator”). Each register holds a single byte. Most operations work on the contents of these registers.  

SP is the stack pointer. I won’t get into the stack yet, but basically this register is decremented every time a byte is pushed onto the stack, and incremented when a byte is popped off the stack.

PC is the program counter – it’s how the processor knows at what point in the program it currently is. It’s like the current line number of an executing script. In the JavaScript simulator the code is assembled starting at memory location $0600, so PC always starts there.

The last section shows the processor flags. Each flag is one bit, so all seven flags live in a single byte. The flags are set by the processor to give information about the previous instruction. More on that later. Read more about the registers and flags here.

Instructions

Instructions in assembly language are like a small set of predefined functions. All instructions take zero or one arguments. Here’s some annotated source code to introduce a few different instructions:

LDA #$c0 ;Load the hex value $c0 into the A register
TAX ;Transfer the value in the A register to X
INX ;Increment the value in the X register
ADC #$c4 ;Add the hex value $c4 to the A register
BRK ;Break – we’re done

Debugger

Monitor Start: $ Length: $

Assemble the code, then turn on the debugger and step through the code, watching the A and X registers. Something slightly odd happens on the line ADC #$c4. You might expect that adding $c4 to $c0 would give $184, but this processor gives the result as $84. What’s up with that?

The problem is, $184 is too big to fit in a single byte (the max is $FF), and the registers can only hold a single byte. It’s OK though; the processor isn’t actually dumb. If you were looking carefully enough, you’ll have noticed that the carry flag was set to 1 after this operation. So that’s how you know.
In the simulator below type (don’t paste) the following code:

LDA #$80
STA $01
ADC $01

Debugger

Monitor Start: $ Length: $

An important thing to notice here is the distinction between ADC #$01 and ADC $01. The first one adds the value $01 to the A register, but the second adds the value stored at memory location $01 to the A register.

Assemble, check the Monitor checkbox, then step through these three instructions. The monitor shows a section of memory, and can be helpful to visualise the execution of programs. STA $01 stores the value of the A register at memory location $01, and ADC $01 adds the value stored at the memory location $01 to the A register. $80 + $80 should equal $100, but because this is bigger than a byte, the A register is set to $00 and the carry flag is set. As well as this though, the zero flag is set. The zero flag is set by all instructions where the result is zero.

A full list of the 6502 instruction set is available here and here (I usually refer to both pages as they have their strengths and weaknesses). These pages detail the arguments to each instruction, which registers they use, and which flags they set. They are your bible.

Exercises

  1. You’ve seen TAX. You can probably guess what TAY, TXA and TYA do, but write some code to test your assumptions.
  2. Rewrite the first example in this section to use the Y register instead of the X register.
  3. The opposite of ADC is SBC (subtract with carry). Write a program that uses this instruction.

Branching

So far we’re only able to write basic programs without any branching logic. Let’s change that.
6502 assembly language has a bunch of branching instructions, all of which branch based on whether certain flags are set or not. In this example we’ll be looking at BNE: “Branch on not equal”.

LDX #$08
decrement:
DEX
STX $0200
CPX #$03
BNE decrement
STX $0201
BRK

Debugger

Monitor Start: $ Length: $

First we load the value $08 into the X register. The next line is a label. Labels just mark certain points in a program so we can return to them later. After the label we decrement X, store it to $0200 (the top-left pixel), and then compare it to the value $03. CPX compares the value in the X register with another value. If the two values are equal, the Z flag is set to 1, otherwise it is set to 0.

The next line, BNE decrement, will shift execution to the decrement label if the Z flag is set to 0 (meaning that the two values in the CPX comparison were not equal), otherwise it does nothing and we store X to $0201, then finish the program.
In assembly language, you’ll usually use labels with branch instructions. When assembled though, this label is converted to a single-byte relative offset (a number of bytes to go backwards or forwards from the next instruction) so branch instructions can only go forward and back around 256 bytes. This means they can only be used to move around local code. For moving further you’ll need to use the jumping instructions.

Exercises

  1. The opposite of BNE is BEQ. Try writing a program that uses BEQ.
  2. BCC and BCS (“branch on carry clear” and “branch on carry set”) are used to branch on the carry flag. Write a program that uses one of these two.

Addressing modes

The 6502 uses a 16-bit address bus, meaning that there are 65536 bytes of memory available to the processor. Remember that a byte is represented by two hex characters, so the memory locations are generally represented as $0000 - $ffff. There are various ways to refer to these memory locations, as detailed below.

With all these examples you might find it helpful to use the memory monitor to watch the memory change. The monitor takes a starting memory location and a number of bytes to display from that location. Both of these are hex values. For example, to display 16 bytes of memory from $c000, enter c000 and 10 into Start and Length, respectively.

Absolute: $c000

With absolute addressing, the full memory location is used as the argument to the instruction. For example:

 
STA $c000 ;Store the value in the accumulator at memory location $c000

Zero page: $c0

All instructions that support absolute addressing (with the exception of the jump instructions) also have the option to take a single-byte address. This type of addressing is called “zero page” – only the first page (the first 256 bytes) of memory is accessible. This is faster, as only one byte needs to be looked up, and takes up less space in the assembled code as well.

Zero page,X: $c0,X

This is where addressing gets interesting. In this mode, a zero page address is given, and then the value of the X register is added. Here is an example:

LDX #$01   ;X is $01
LDA #$aa   ;A is $aa
STA $a0,X ;Store the value of A at memory location $a1
INX        ;Increment X
STA $a0,X ;Store the value of A at memory location $a2

If the result of the addition is larger than a single byte, the address wraps around. For example:

LDX #$05
STA $ff,X ;Store the value of A at memory location $04

Zero page,Y: $c0,Y

This is the equivalent of zero page,X, but can only be used with LDX and STX.

Absolute,X and absolute,Y: $c000,X and $c000,Y

These are the absolute addressing versions of zero page,X and zero page,Y. For example:

LDX #$01
STA $0200,X ;Store the value of A at memory location $0201

Immediate: #$c0

Immediate addressing doesn’t strictly deal with memory addresses – this is the mode where actual values are used. For example, LDX #$01 loads the value $01 into the X register. This is very different to the zero page instruction LDX $01 which loads the value at memory location $01 into the X register.

Relative: $c0 (or label)

Relative addressing is used for branching instructions. These instructions take a single byte, which is used as an offset from the following instruction.
Assemble the following code, then click the Hexdump button to see the assembled code.

LDA #$01
CMP #$02
BNE notequal
STA $22
notequal:
BRK

Debugger

Monitor Start: $ Length: $

The hex should look something like this:

 
a9 01 c9 02 d0 02 85 22 00

a9 and c9 are the processor opcodes for immediate-addressed LDA and CMP respectively. 01 and 02 are the arguments to these instructions. d0 is the opcode for BNE, and its argument is 02. This means “skip over the next two bytes” (85 22, the assembled version of STA $22). Try editing the code so STA takes a two-byte absolute address rather than a single-byte zero page address (e.g. change STA $22 to STA $2222). Reassemble the code and look at the hexdump again – the argument to BNE should now be 03, because the instruction the processor is skipping past is now three bytes long.

Implicit

Some instructions don’t deal with memory locations (e.g. INX – increment the X register). These are said to have implicit addressing – the argument is implied by the instruction.

Indirect: ($c000)

Indirect addressing uses an absolute address to look up another address. The first address gives the least significant byte of the address, and the following byte gives the most significant byte. That can be hard to wrap your head around, so here’s an example:

LDA #$01
STA $f0
LDA #$cc
STA $f1
JMP ($00f0) ;dereferences to $cc01

Debugger

Monitor Start: $ Length: $

In this example, $f0 contains the value $01 and $f1 contains the value $cc. The instruction JMP ($f0) causes the processor to look up the two bytes at $f0 and $f1 ($01 and $cc) and put them together to form the address $cc01, which becomes the new program counter. Assemble and step through the program above to see what happens. I’ll talk more about JMP in the section on Jumping.

Indexed indirect: ($c0,X)

This one’s kinda weird. It’s like a cross between zero page,X and indirect. Basically, you take the zero page address, add the value of the X register to it, then use that to look up a two-byte address. For example:

LDX #$01
LDA #$05
STA $01
LDA #$06
STA $02
LDY #$0a
STY $0605
LDA ($00,X)

Debugger

Monitor Start: $ Length: $

Memory locations $01 and $02 contain the values $05 and $06 respectively. Think of ($00,X) as ($00 + X). In this case X is $01, so this simplifies to ($01). From here things proceed like standard indirect addressing – the two bytes at $01 and $02 ($05 and $06) are looked up to form the address $0605. This is the address that the Y register was stored into in the previous instruction, so the A register gets the same value as Y, albeit through a much more circuitous route. You won’t see this much.

Indirect indexed: ($c0),Y

Indirect indexed is like indexed indirect but less insane. Instead of adding the X register to the address before dereferencing, the zero page address is dereferenced, and the Y register is added to the resulting address.

LDY #$01
LDA #$03
STA $01
LDA #$07
STA $02
LDX #$0a
STX $0704
LDA ($01),Y

Debugger

Monitor Start: $ Length: $

In this case, ($01) looks up the two bytes at $01 and $02: $03 and $07. These form the address $0703. The value of the Y register is added to this address to give the final address $0704.

Exercise

  1. Try to write code snippets that use each of the 6502 addressing modes. Remember, you can use the monitor to watch a section of memory.

The stack

The stack in a 6502 processor is just like any other stack – values are pushed onto it and popped (“pulled” in 6502 parlance) off it. The current depth of the stack is measured by the stack pointer, a special register. The stack lives in memory between $0100 and $01ff. The stack pointer is initially $ff, which points to memory location $01ff. When a byte is pushed onto the stack, the stack pointer becomes $fe, or memory location $01fe, and so on.
Two of the stack instructions are PHA and PLA, “push accumulator” and “pull accumulator”. Below is an example of these two in action.

LDX #$00
LDY #$00
firstloop:
TXA
STA $0200,Y
PHA
INX
INY
CPY #$10
BNE firstloop ;loop until Y is $10
secondloop:
PLA
STA $0200,Y
INY
CPY #$20 ;loop until Y is $20
BNE secondloop

Debugger

Monitor Start: $ Length: $

X holds the pixel colour, and Y holds the position of the current pixel. The first loop draws the current colour as a pixel (via the A register), pushes the colour to the stack, then increments the colour and position. The second loop pops the stack, draws the popped colour as a pixel, then increments the position. As should be expected, this creates a mirrored pattern.

Jumping

Jumping is like branching with two main differences. First, jumps are not conditionally executed, and second, they take a two-byte absolute address. For small programs, this second detail isn’t very important, as you’ll mostly be using labels, and the assembler works out the correct memory location from the label. For larger programs though, jumping is the only way to move from one section of the code to another.

JMP

JMP is an unconditional jump. Here’s a really simple example to show it in action:

LDA #$03
JMP there
BRK
BRK
BRK
there:
STA $0200

Debugger

Monitor Start: $ Length: $

JSR/RTS

JSR and RTS (“jump to subroutine” and “return from subroutine”) are a dynamic duo that you’ll usually see used together. JSR is used to jump from the current location to another part of the code. RTS returns to the previous position. This is basically like calling a function and returning.  The processor knows where to return to because JSR pushes the address minus one of the next instruction onto the stack before jumping to the given location. RTS pops this location, adds one to it, and jumps to that location. An example:

JSR init
JSR loop
JSR end

init:
LDX #$00
RTS

loop:
INX
CPX #$05
BNE loop
RTS

end:
BRK

Debugger

Monitor Start: $ Length: $

The first instruction causes execution to jump to the init label. This sets X, then returns to the next instruction, JSR loop. This jumps to the loop label, which increments X until it is equal to $05. After that we return to the next instruction, JSR end, which jumps to the end of the file. This illustrates how JSR and RTS can be used together to create modular code.

Creating a game

Now, let’s put all this knowledge to good use, and make a game! We’re going to be making a really simple version of the classic game ‘Snake’.
The simulator widget below contains the entire source code of the game. I’ll explain how it works in the following sections.
Willem van der Jagt made a fully annotated gist of this source code, so follow along with that for more details.

; ___ _ __ ___ __ ___
; / __|_ _ __ _| |_____ / /| __|/ \_ )
; \__ \ ‘ \/ _` | / / -_) _ \__ \ () / /
; |___/_||_\__,_|_\_\___\___/___/\__/___|

; Change direction: W A S D

; $00-01 => screen location of apple
; $10-11 => screen location of snake head
; $12-?? => snake body (in byte pairs)
; $02 => direction (1 => up, 2 => right, 4 => down, 8 => left)
; $03 => snake length

jsr init
jsr loop

init:
jsr initSnake
jsr generateApplePosition
rts

initSnake:
lda #2 ;start direction
sta $02
lda #4 ;start length
sta $03
lda #$11
sta $10
lda #$10
sta $12
lda #$0f
sta $14
lda #$04
sta $11
sta $13
sta $15
rts

generateApplePosition:
;load a new random byte into $00
lda $fe
sta $00

;load a new random number from 2 to 5 into $01
lda $fe
and #$03 ;mask out lowest 2 bits
clc
adc #2
sta $01

rts

loop:
jsr readKeys
jsr checkCollision
jsr updateSnake
jsr drawApple
jsr drawSnake
jsr spinWheels
jmp loop

readKeys:
lda $ff
cmp #$77
beq upKey
cmp #$64
beq rightKey
cmp #$73
beq downKey
cmp #$61
beq leftKey
rts
upKey:
lda #4
bit $02
bne illegalMove

lda #1
sta $02
rts
rightKey:
lda #8
bit $02
bne illegalMove

lda #2
sta $02
rts
downKey:
lda #1
bit $02
bne illegalMove

lda #4
sta $02
rts
leftKey:
lda #2
bit $02
bne illegalMove

lda #8
sta $02
rts
illegalMove:
rts

checkCollision:
jsr checkAppleCollision
jsr checkSnakeCollision
rts

checkAppleCollision:
lda $00
cmp $10
bne doneCheckingAppleCollision
lda $01
cmp $11
bne doneCheckingAppleCollision

;eat apple
inc $03
inc $03 ;increase length
jsr generateApplePosition
doneCheckingAppleCollision:
rts

checkSnakeCollision:
ldx #2 ;start with second segment
snakeCollisionLoop:
lda $10,x
cmp $10
bne continueCollisionLoop

maybeCollided:
lda $11,x
cmp $11
beq didCollide

continueCollisionLoop:
inx
inx
cpx $03 ;got to last section with no collision
beq didntCollide
jmp snakeCollisionLoop

didCollide:
jmp gameOver
didntCollide:
rts

updateSnake:
ldx $03 ;location of length
dex
txa
updateloop:
lda $10,x
sta $12,x
dex
bpl updateloop

lda $02
lsr
bcs up
lsr
bcs right
lsr
bcs down
lsr
bcs left
up:
lda $10
sec
sbc #$20
sta $10
bcc upup
rts
upup:
dec $11
lda #$1
cmp $11
beq collision
rts
right:
inc $10
lda #$1f
bit $10
beq collision
rts
down:
lda $10
clc
adc #$20
sta $10
bcs downdown
rts
downdown:
inc $11
lda #$6
cmp $11
beq collision
rts
left:
dec $10
lda $10
and #$1f
cmp #$1f
beq collision
rts
collision:
jmp gameOver

drawApple:
ldy #0
lda $fe
sta ($00),y
rts

drawSnake:
ldx #0
lda #1
sta ($10,x)
ldx $03
lda #0
sta ($10,x)
rts

spinWheels:
ldx #0
spinloop:
nop
nop
dex
bne spinloop
rts

gameOver:

Debugger

Monitor Start: $ Length: $

Overall structure

After the initial block of comments (lines starting with semicolons), the first two lines are:

jsr init
jsr loop
 

init and loop are both subroutines. init initializes the game state, and loop is the main game loop. The loop subroutine itself just calls a number of subroutines sequentially, before looping back on itself:

loop:
  jsr readkeys
  jsr checkCollision
  jsr updateSnake
  jsr drawApple
  jsr drawSnake
  jsr spinwheels
  jmp loop

First, readkeys checks to see if one of the direction keys (W, A, S, D) was pressed, and if so, sets the direction of the snake accordingly. Then, checkCollision checks to see if the snake collided with itself or the apple. updateSnake updates the internal representation of the snake, based on its direction. Next, the apple and snake are drawn. Finally, spinWheels makes the processor do some busy work, to stop the game from running too quickly. Think of it like a sleep command. The game keeps running until the snake collides with the wall or itself.

Zero page usage

The zero page of memory is used to store a number of game state variables, as noted in the comment block at the top of the game. Everything in $00, $01 and $10 upwards is a pair of bytes representing a two-byte memory location that will be looked up using indirect addressing. These memory locations will all be between $0200 and $05ff – the section of memory corresponding to the simulator display. For example, if $00 and $01 contained the values $01 and $02, they would be referring to the second pixel of the display ($0201 – remember, the least significant byte comes first in indirect addressing).

The first two bytes hold the location of the apple. This is updated every time the snake eats the apple. Byte $02 contains the current direction. 1 means up, 2 right, 4 down, and 8 left. The reasoning behind these numbers will become clear later.
Finally, byte $03 contains the current length of the snake, in terms of bytes in memory (so a length of 4 means 2 pixels).

Initialization

The init subroutine defers to two subroutines, initSnake and generateApplePosition. initSnake sets the snake direction, length, and then loads the initial memory locations of the snake head and body. The byte pair at $10 contains the screen location of the head, the pair at $12 contains the location of the single body segment, and $14 contains the location of the tail (the tail is the last segment of the body and is drawn in black to keep the snake moving). This happens in the following code:

lda #$11
sta $10
lda #$10
sta $12
lda #$0f
sta $14
lda #$04
sta $11
sta $13
sta $15

This loads the value $11 into the memory location $10, the value $10 into $12, and $0f into $14. It then loads the value $04 into $11, $13 and $15. This leads to memory like this:

0010: 11 04 10 04 0f 04

which represents the indirectly-addressed memory locations $0411, $0410 and $04ff (three pixels in the middle of the display). I’m labouring this point, but it’s important to fully grok how indirect addressing works.

The next subroutine, generateApplePosition, sets the apple location to a random position on the display. First, it loads a random byte into the accumulator ($fe is a random number generator in this simulator). This is stored into $00. Next, a different random byte is loaded into the accumulator, which is then AND-ed with the value $03. This part requires a bit of a detour.

The hex value $03 is represented in binary as 00000111. The AND opcode performs a bitwise AND of the argument with the accumulator. For example, if the accumulator contains the binary value 01010101, then the result of AND with 00000111 will be 00000101.

The effect of this is to mask out the least significant three bytes of the accumulator, setting the others to zero. This converts a number in the range of 0–255 to a number in the range of 0–3.
After this, the value 2 is added to the accumulator, to create a final random number in the range 2–5.

The result of this subroutine is to load a random byte into $00, and a random number between 2 and 5 into $01. Because the least significant byte comes first with indirect addressing, this translates into a memory address between $0200 and $05ff: the exact range used to draw the display.

The game loop

Nearly all games have at their heart a game loop. All game loops have the same basic form: accept user input, update the game state, and render the game state. This loop is no different.

Reading the input

The first subroutine, readKeys, takes the job of accepting user input. The memory location $ff holds the ascii code of the most recent key press in this simulator. The value is loaded into the accumulator, then compared to $77 (the hex code for W), $64 (D), $73 (S) and $61. If any of these comparisons are successful, the program branches to the appropriate section. Each section (upKey, rightKey, etc.) first checks to see if the current direction is the opposite of the new direction. This requires another little detour.

As stated before, the four directions are represented internally by the numbers
1, 2, 4 and 8. Each of these numbers is a power of 2, thus they are represented by a binary number with a single 1:

1 => 0001 (up)
2 => 0010 (right)
4 => 0100 (down)
8 => 1000 (left)

The BIT opcode is similar to AND, but the calculation is only used to set the zero flag – the actual result is discarded. The zero flag is set only if the result of AND-ing the accumulator with argument is zero. When we’re looking at powers of two, the zero flag will only be set if the two numbers are not the same. For example, 0001 AND 0001 is not zero, but 0001 AND 0010 is zero.

So, looking at upKey, if the current direction is down (4), the bit test will be zero. BNE means “branch if the zero flag is clear”, so in this case we’ll branch to illegalMove, which just returns from the subroutine. Otherwise, the new direction (1 in this case) is stored in the appropriate memory location.

Updating the game state

The next subroutine, checkCollision, defers to checkAppleCollision and checkSnakeCollision. checkAppleCollision just checks to see if the two bytes holding the location of the apple match the two bytes holding the location of the head. If they do, the length is increased and a new apple position is generated.
checkSnakeCollision loops through the snake’s body segments, checking each byte pair against the head pair. If there is a match, then game over.

After collision detection, we update the snake’s location. This is done at a high level like so: First, move each byte pair of the body up one position in memory. Second, update the head according to the current direction. Finally, if the head is out of bounds, handle it as a collision. I’ll illustrate this with some ascii art. Each pair of brackets contains an x,y coordinate rather than a pair of bytes for simplicity.

  0    1    2    3    4
Head                 Tail
[1,5][1,4][1,3][1,2][2,2]    Starting position
[1,5][1,4][1,3][1,2][1,2]    Value of (3) is copied into (4)
[1,5][1,4][1,3][1,2][1,2]    Value of (2) is copied into (3)
[1,5][1,4][1,3][1,2][1,2]    Value of (1) is copied into (2)
[1,5][1,4][1,3][1,2][1,2]    Value of (0) is copied into (1)
[0,4][1,4][1,3][1,2][1,2]    Value of (0) is updated based on direction

At a low level, this subroutine is slightly more complex. First, the length is loaded into the X register, which is then decremented. The snippet below shows the starting memory for the snake.

 
Memory location: $10 $11 $12 $13 $14 $15

Value:           $11 $04 $10 $04 $0f $04

The length is initialized to 4, so X starts off as 3. LDA $10,x loads the value of $13 into A, then STA $12,x stores this value into $15. X is decremented, and we loop. Now X is 2, so we load $12 and store it into $14. This loops while X is positive (BPL means “branch if positive”).

Once the values have been shifted down the snake, we have to work out what to do with the head. The direction is first loaded into A. LSR means “logical shift right”, or “shift all the bits one position to the right”. The least significant bit is shifted into the carry flag, so if the accumulator is 1, after LSR it is 0, with the carry flag set.

To test whether the direction is 1, 2, 4 or 8, the code continually shifts right until the carry is set. One LSR means “up”, two means “right”, and so on.
The next bit updates the head of the snake depending on the direction. This is probably the most complicated part of the code, and it’s all reliant on how memory locations map to the screen, so let’s look at that in more detail.

You can think of the screen as four horizontal strips of 32 × 8 pixels. These strips map to $0200-$02ff, $0300-$03ff, $0400-$04ff and $0500-$05ff. The first rows of pixels are $0200-$021f, $0220-$023f, $0240-$025f, etc.

As long as you’re moving within one of these horizontal strips, things are simple. For example, to move right, just incrememnt the least significant byte (e.g. $0200 becomes $0201). To go down, add $20 (e.g. $0200 becomes $0220). Left and up are the reverse.

Going between sections is more complicated, as we have to take into account the most significant byte as well. For example, going down from $02e1 should lead to $0301. Luckily, this is fairly easy to accomplish. Adding $20 to $e1 results in $01 and sets the carry bit. If the carry bit was set, we know we also need to increment the most significant byte.

After a move in each direction, we also need to check to see if the head would become out of bounds. This is handled differently for each direction. For left and right, we can check to see if the head has effectively “wrapped around”. Going right from $021f by incrementing the least significant byte would lead to $0220, but this is actually jumping from the last pixel of the first row to the first pixel of the second row. So, every time we move right, we need to check if the new least significant byte is a multiple of $20. This is done using a bit check against the mask $1f. Hopefully the illustration below will show you how masking out the lowest 5 bits reveals whether a number is a multiple of $20 or not.

 
$20: 0010 0000
$40: 0100 0000
$60: 0110 0000
$1f: 0001 1111

I won’t explain in depth how each of the directions work, but the above explanation should give you enough to work it out with a bit of study.

Rendering the game

Because the game state is stored in terms of pixel locations, rendering the game is very straightforward. The first subroutine, drawApple, is extremely simple. It sets Y to zero, loads a random colour into the accumulator, then stores this value into ($00),y. $00 is where the location of the apple is stored, so ($00),y dereferences to this memory location. Read the “Indirect indexed” section in Addressing modes for more details.

Next comes drawSnake. This is pretty simple too. X is set to zero and A to one. We then store A at ($10,x). $10 stores the two-byte location of the head, so this draws a white pixel at the current head position. Next we load $03 into X. $03 holds the length of the snake, so ($10,x) in this case will be the location of the tail. Because A is zero now, this draws a black pixel over the tail. As only the head and the tail of the snake move, this is enough to keep the snake moving.

The last subroutine, spinWheels, is just there because the game would run too fast otherwise. All spinWheels does is count X down from zero until it hits zero again. The first dex wraps, making X #$ff.

—————————————

Rosettacode is a great place for geting code tidbits. Here is a little program we run on the nslu2 and the other embedded projects. Here is a quickie program for taking notes.

Screenshot - 01112015 - 04:55:45 PM

 PROGRAM-ID. NOTES.

 ENVIRONMENT DIVISION.
 INPUT-OUTPUT SECTION.
 FILE-CONTROL.
 SELECT OPTIONAL notes ASSIGN TO "NOTES.TXT"
 ORGANIZATION LINE SEQUENTIAL
 FILE STATUS note-status.

 DATA DIVISION.
 FILE SECTION.
 FD notes.
 01 note-record PIC X(256).

 LOCAL-STORAGE SECTION.
 01 note-status PIC 99.
 88 notes-ok VALUE 0 THRU 9.

 01 date-now.
 03 current-year PIC 9(4).
 03 current-month PIC 99.
 03 current-day PIC 99.

 01 time-now.
 03 current-hour PIC 99.
 03 current-min PIC 99.
 03 current-sec PIC 99.

 01 args PIC X(256).

 PROCEDURE DIVISION.
 DECLARATIVES.
 note-error SECTION.
 USE AFTER STANDARD ERROR PROCEDURE ON notes.

 DISPLAY "Error using NOTES.TXT. Error code: " note-status
 .
 END DECLARATIVES.

 main.
 ACCEPT args FROM COMMAND-LINE

*>  If there are no args, display contents of NOTES.TXT.
 IF args = SPACES
 OPEN INPUT notes

 PERFORM FOREVER
*> READ has no syntax highlighting, but END-READ does.
*>  Go figure.
 READ notes
 AT END
 EXIT PERFORM

 NOT AT END
 DISPLAY FUNCTION TRIM(note-record)
 END-READ
 END-PERFORM
 ELSE
 OPEN EXTEND notes

*>Write date and time to file.
 ACCEPT date-now FROM DATE YYYYMMDD
 ACCEPT time-now FROM TIME
 STRING current-year "-" current-month "-" current-day
 " " current-hour ":" current-min ":" current-sec
 INTO note-record
 WRITE note-record

*>  Write arguments to file as they were passed.
 STRING X"09", args INTO note-record
 WRITE note-record
 END-IF

 CLOSE notes

 GOBACK.

$ cobc -x -free notes.cob

—————————————

Chicken thighs. Perfect for game day.

SUNP0002

Good day.

Advertisements