Source code to my work in progress

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

User avatar
clueless
Posts: 496
Joined: Sun Sep 07, 2008 7:27 am
Location: Seatlle, WA, USA

Source code to my work in progress

Post by clueless »

Hello Everyone,

A few people have asked to see my source code. Please consider this disclaimer:

0) If my code helps anyone I'd like to know. Since I'm just starting out, I fail to see how it would be useful to anyone though...

1) I'm not an expert NES developer - I'm a newbie. Please don't assume that I am good at this just because I'm sharing this code with the community. I used to write 6502 asm on the Apple IIe in high school. That is the extent of my 6502 knowledge.

2) I've only ever played a handful of NES games. Mostly common (ie, cheap) RPGs and action/adventure. The vast majority of the visual tricks that I see people make reference to I've never seen nor would I recognize the game.

3) My code is ugly and contains swear words.

4) All aspects of the code are a work in progress. No section of code is considered "complete".

5) I develop the game on a Gentoo linux box, but I test it using NES emulators on Windows XP. So if you have a relatively recent Linux box it should compile ***

6) *** One of my custom build tools requires the perl module "Image::BMP" in order to convert a BMP file into a ".s" file to be assmebled. You won't be able to succesfully build the project without this perl module.

7) My svn and trac stuff is hosted on an apache box with a self-signed certificate. You'll need to accept the warnings from your web browser.

8) I've never tries to compile this on Windows w/ cygwin, nor on FreeBSD or Solaris (even though I have access to all of them).

All of that being said, this is what you do in a nut-shell:

Code: Select all

svn co https://www.ecoligames.com/svn/nes-game/trunk /tmp/nes-game
cd /tmp/nes-game
make
ls -l nes-game.nes   ### this is the final output.
You can refresh the code with my latest updates at any time via:

Code: Select all

cd /tmp/nes-game
svn up
make clean all
You can browse the source code in your web browser here:

https://www.ecoligames.com/trac/nes-gam ... wser/trunk

This URL will show you the revision history:

https://www.ecoligames.com/trac/nes-gam ... ate=Update


Please be kind to my server; I have DSL :)


ps- I have two other NES related projects. I don't have trac set up for them yet, but they should be read-only in SVN:

svn co https://www.ecoligames.com/svn/FariaEditor /tmp/FariaEditor
svn co https://www.ecoligames.com/svn/Crystalis /tmp/Crystalis

I've mentioned the Faria editor before. It is a window program and requires MSDev Studio 98 (msdev v6) to compile. It probably needs the 2003 February platform SDK and some other magical incantations to compile, but I have no idea (my windows dev box has all that crap and it "just works").

The "Crystalis" project is my attempt to create a tool to assist in reverse engineering the game. It is mostly my notes and a tool to dump all of the maps from the game as PBM and GIF files. All of these tools should compile on any recent Gentoo box (you'll need to emerge "media-libs/netpbm" first).
User avatar
Banshaku
Posts: 2404
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Post by Banshaku »

I have a question regarding your code. I'm a newbie too so don't be too hard on me :)

I checked your files and you seems to re-use the same segment in many files. What is the advantage of this? My first impression was that 1 segment is declared once and that's it (impression only, cannot find any doc in ca65 regarding how to use it). In which order the code will be compiled since you have many "kernel" segments?

Then, I saw the .proc preprocessor. I didn't know about that and it seems to reduce the scope of the labels to that specific function, which is useful. But when you define a proc, you define a label with the same name inside. The doc (again ca65 one) doesn't say anything about this. Why do we need to do that? I want to use proc but that part puzzled me.

I may have more question later once I browse you code more.

By the way, how comfortable are you with makefile? I would love to find a nice tutorial on how to make more advanced ones. In what I do in my every day jobs I don't need make files so I don't have much the chance to practice it or to find people that have the knowledge about it.
User avatar
clueless
Posts: 496
Joined: Sun Sep 07, 2008 7:27 am
Location: Seatlle, WA, USA

Post by clueless »

Banshaku wrote:I have a question regarding your code. I'm a newbie too so don't be too hard on me :)

I checked your files and you seems to re-use the same segment in many files. What is the advantage of this? My first impression was that 1 segment is declared once and that's it (impression only, cannot find any doc in ca65 regarding how to use it). In which order the code will be compiled since you have many "kernel" segments?
The assembler and linker work together. The linker will splice together all of the assembled bytes for each segment. The linker uses a config file (./src/linker.cfg) to determine which order to place the segments in the .NES file. The config file also gives the final virtual starting addresses. My ROM doesn't use bank switching yet, but when it does the linker file will show many segments (ROM0, ROM1, ROM2, etc...) all at the same "start=" address. My segment "KERNEL" is intended to be the last ROM segment, the one that is mapped to "$C000" in the CPUs address space and is never swapped out.

Let's say that I had two source files that look like this:

(file 'a.s'):

Code: Select all

.segment "FOO"
p:   .byte "hello "
.segment "BAR"
q:   .byte "bye "
.segment "FOO"
     .byte "world"
(file 'b.s'):

Code: Select all

.segment "BAR"
     .byte "cruel world"
And a linker file (paraphrasing, because I'm too lazy to go read the exact syntax document) that looked something like this:

Code: Select all

MEMORY { 
   ABC: start = $8000, size = $4000, fill = yes;
   DEF: start = $8000, size = $4000, fill = yes;
}

SEGMENTS {
   FOO:  load = ABC;
   BAR:  load = DEF;
}
The final output file would be 32768 bytes ($8000 bytes).
It would begin with the string "hello world", followed by lots of zeros.
Exactly halfway into the file (physical offset $4000) it would have the string "bye cruel world" OR "cruel worldbye " (more on this later), followed by more zeros.

The linker will link the segment pieces in the same order that they are passed on the command line. So to get "bye cruel world", you would do:

Code: Select all

ld65 -c linker.cfg a.o b.o -o output.bin
To get "cruel worldbye ":

Code: Select all

ld65 -c linker.cfg b.o a.o -o output.bin
Note that both "ABC" and "DEF" begin at the same address in the memory map. These segments will be at different byte offsets into the resulting binary, but the values of the symbols "p" and "q" will both be "$8000". This facilitates writing code that will be bank-switched at run-time. The "cc65" system is incredibly powerful in this regard.

More info is available in the documentation for the linker [1].
Banshaku wrote:Then, I saw the .proc preprocessor. I didn't know about that and it seems to reduce the scope of the labels to that specific function, which is useful. But when you define a proc, you define a label with the same name inside. The doc (again ca65 one) doesn't say anything about this. Why do we need to do that? I want to use proc but that part puzzled me.

(edit, 2008-09-25, 9:46am CST). I am way wrong on how scopes are implemented in ca65. http://www.cc65.org/doc/ca65-6.html has all of the details. I'm too lazy to properly edit this post. I'm at work and should be writing C++ code ;)


THE CRAP BELOW HERE IS WRONG!!!!!

The is mostly it. ".proc" isn't consumed by a pre-processor. It is a directive for the assembler itself. It does create "scope" for local labels. Every local label that does not have the exact same name as the procedure itself gets some prefix based on the procedure. So:

Code: Select all

.proc   abc
abc:
       ldx #$00
       stx $ff
q:     rts
.endproc

.proc   def
def:
         ldy #$ea
q:      rts
.endproc
Creates four labels: "abc", "<unknown hash of 'abc'>_q", "def", "<unknown hash of 'def'>_q". So "q" is "local" to each procedure. I do not know the hashing algorithm. It should not be relied on (treat it as a black box). The one downside to all of this is that it makes it difficult to create multiple entry points for a single logical function / procedure.

Banshaku wrote:I may have more question later once I browse you code more.

By the way, how comfortable are you with makefile? I would love to find a nice tutorial on how to make more advanced ones. In what I do in my every day jobs I don't need make files so I don't have much the chance to practice it or to find people that have the knowledge about it.
I love makefiles. One you realize that they are not scripts to be executed linearly, but are directed acyclic graphs [2] that represent dependency information they become easier to think about. The rest is all about macro expansion and (the part that I hate) implicit rules.

Years ago I came across an article titles "Recursive Makefiles Considered Harmful" [3]. I read it and agree. Although my Makefiles don't look like those from the author of that article, I don't use recursive nestings of them (ie, one in each directory of the project, each to be invoked opaquely by a separate 'make' process).

I'm not a Makefile expert. I get lost in other people's Makefiles, especially ones generated by 'automake' and 'autoconf'. One day I will rewrite the Makefile for this project. At that time I'll document it better and update this forum.

I hope all of this helps.

Disclaimer - I did nto verify the accuracy of any of the above ramblings. I could be wrong. But the above is what I think happens inside the linker. :) YMMV.


[1] http://www.cc65.org/doc/ld65.html

[2] http://en.wikipedia.org/wiki/Directed_acyclic_graph

[3] http://cj5.info/pmiller/books/rmch/
Last edited by clueless on Thu Sep 25, 2008 7:47 am, edited 1 time in total.
User avatar
Banshaku
Posts: 2404
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Post by Banshaku »

clueless wrote:Exactly halfway into the file (physical offset $4000) it would have the string "bye cruel world" OR "cruel worldbye " (more on this later), followed by more zeros.
This is the part that I thought could happen: depending of the order you link the files, the content could be in a different order since you use the same segment. Wouldn't that be an issue sometime?

If you define 2 segments that points at the same address, this mean you would have 2 banks in that case. ok. So if I follow that logic, to allows code to be split in multiple files but to be in the same bank, you would have to use the same segment. Am I correct?
The is mostly it. ".proc" isn't consumed by a pre-processor. It is a directive for the assembler itself. It does create "scope" for local labels. Every local label that does not have the exact same name as the procedure itself gets some prefix based on the procedure. So:
So technically if you create a label with the same name as the proc, it will not be in the proc scope. I do understand that point but when do you need this functionality? Since I can do jsr myProc without defining the label, I cannot see the use yet because of my lack of experience in 6502.
I'm not a Makefile expert. I get lost in other people's Makefiles, especially ones generated by 'automake' and 'autoconf'. One day I will rewrite the Makefile for this project. At that time I'll document it better and update this forum.
Thanks to your makefile, I was able to fix one of my issue I had. Still have a few other one but I guess I will figure out someday. I never was able to find a tutorial that was useful more than a simple make file. After that, you're left with the man page which sometime doesn't help you much when you don't know how it work in the first place.
User avatar
clueless
Posts: 496
Joined: Sun Sep 07, 2008 7:27 am
Location: Seatlle, WA, USA

Post by clueless »

Banshaku wrote:
clueless wrote:Exactly halfway into the file (physical offset $4000) it would have the string "bye cruel world" OR "cruel worldbye " (more on this later), followed by more zeros.
This is the part that I thought could happen: depending of the order you link the files, the content could be in a different order since you use the same segment. Wouldn't that be an issue sometime?
For structured data that needs to be interprited in a certain way, probably. But generally not for chunks of code. Any code or data can be located if you know what bank it is in and what the symbol name (and hence, address assigned via the linker) is. I have never attempted to write bank switched code that would execute at different addresses.. ie, a code bank that could be mapped to $8000 or $c000 at run time.
Banshaku wrote:If you define 2 segments that points at the same address, this mean you would have 2 banks in that case. ok. So if I follow that logic, to allows code to be split in multiple files but to be in the same bank, you would have to use the same segment. Am I correct?
Yes
Banshaku wrote:
The is mostly it. ".proc" isn't consumed by a pre-processor. It is a directive for the assembler itself. It does create "scope" for local labels. Every local label that does not have the exact same name as the procedure itself gets some prefix based on the procedure. So:
So technically if you create a label with the same name as the proc, it will not be in the proc scope. I do understand that point but when do you need this functionality? Since I can do jsr myProc without defining the label, I cannot see the use yet because of my lack of experience in 6502.
Technically, I don't need to use '.proc'. I do so the get scoping for my local labels. I could write my code such that every label has a globally unique name. It is just a convienience. Plus for the past 15 years (ouch, I'm getting old) I've been writing structured code (c, c++, pascal), and my mind just works that way.
Banshaku wrote:
I'm not a Makefile expert. I get lost in other people's Makefiles, especially ones generated by 'automake' and 'autoconf'. One day I will rewrite the Makefile for this project. At that time I'll document it better and update this forum.
Thanks to your makefile, I was able to fix one of my issue I had. Still have a few other one but I guess I will figure out someday. I never was able to find a tutorial that was useful more than a simple make file. After that, you're left with the man page which sometime doesn't help you much when you don't know how it work in the first place.
I'm glad that it helped you :) No one has ever (that I know of) every learned anything from my code before. They usually freak out and run away from it.

ps - A google search for "makefile tutorial" will give you lots of useful tutorials. Most seem to be hosted on the personal web pages of college professors and TAs (teaching assistants in the USA) of various computer science departments.
User avatar
Banshaku
Posts: 2404
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Post by Banshaku »

clueless wrote:Technically, I don't need to use '.proc'. I do so the get scoping for my local labels. I could write my code such that every label has a globally unique name. It is just a convienience. Plus for the past 15 years (ouch, I'm getting old) I've been writing structured code (c, c++, pascal), and my mind just works that way.
When I started to define my procedure, it was only labels and I found that quite inconvenient because of the scoping issue. After reading you code, I searched more about proc and saw that this little thing was quite useful. Now I'm starting to use it and I'm very happy with it. I separate code by module to make the scope even more obvious and define header files that contains imports of the procedures that you can use in that module. So I'm from a structured code background too and trying to see how much I can use of that knowledge on that platform.

I was in college in 1993, learned some pascal, turbo c and all kind of old things (even cobol! argg...) but started to program professionally in 2000 so don't feel too old, I'm in the same boat as you :) These days it's more java, c# and web stuff (action script etc...) that I don't always like anyway. That's why I do some 6502 as a hobby, to change my mind from the boring stuff at work.
I'm glad that it helped you :) No one has ever (that I know of) every learned anything from my code before. They usually freak out and run away from it.
I didn't know how it work at first (in your make file) but I saw things that I never saw before and it helped me to find new things that I didn't know. Those new things fixed one issue I wanted to improve in my make file. Other people views can sometime make you think differently.
ps - A google search for "makefile tutorial" will give you lots of useful tutorials. Most seem to be hosted on the personal web pages of college professors and TAs (teaching assistants in the USA) of various computer science departments.
I did but it always end up on the same basic stuff and the more advanced stuff (the things that you're really looking for) is always left out. Since you're new to the thing, it's hard to search when you don't know what you're looking for in the first place (I would have never known about addprefix without seeing it in your make file).
User avatar
blargg
Posts: 3717
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

No one has ever (that I know of) every learned anything from my code before. They usually freak out and run away from it.
Probably because it's organized just the same way a substantial C program would be, with separate modules and a makefile. That's a good thing too :)

BTW, people here more often use labels prefixed with "@", which makes them "local" between the closest two non-@ labels. It's a less-formal scoping mechanism than ".proc".

I was looking over the source a few days ago and noticed you documented which registers were destroyed. Wouldn't it be better to document which were preserved, since it's better to assume destruction than preservation of anything unmentioned?
User avatar
clueless
Posts: 496
Joined: Sun Sep 07, 2008 7:27 am
Location: Seatlle, WA, USA

Post by clueless »

blargg wrote:I was looking over the source a few days ago and noticed you documented which registers were destroyed. Wouldn't it be better to document which were preserved, since it's better to assume destruction than preservation of anything unmentioned?
6 of one, 1/2 dozen of the other I guess. Maybe. Since there are only three registers, I don't know if it really matters. In my C code, I usually document which globals are munged. In my 6502 asm, I find it difficult to wrote code that doesn't destroy registers. It takes CPU cycles to have the callee save them on the stack. So I don't bother unless I know that it must be saved.

So far I really haven't had a problem of a caller needing to preserve something that a callee is going to destroy. More often, I run into problems where I just don't have enough registers period. 8088 and MIPS R2000 have spoiled me. :) For example, in my code that converts meta-tiles into tiles and stores them in PPU VRAM. [1]

To deal with this, I have a set of generic "words" (tmp1_ptr ... tmp5_ptr) that I store in zero-page. Any function is allowed to use them, and any function may destroy them. If I need something in one preserved, then that caller must preserve it somehow.

I don't know if this is a good practice or not. It is kind of like a 'COMM' section from a really old main-frame program (I'm not that old... I just know enough about that stuff to sound dangerous).

Note - a quick Google search doesn't show a description of 'comm section' that matches what I remember. It is kind of like a union of all private .bss segments from each module linked into a program. That is, when a given section of code is in use it can clobber whatever is in this region of memory, but when that code goes out of scope, someone else may also clobber that memory. I think... Heck, I don't know. But in my NES code that is how I use it :)



[1] https://www.ecoligames.com/svn/nes-game ... map-draw.s
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

clueless wrote:I have a set of generic "words" (tmp1_ptr ... tmp5_ptr) that I store in zero-page. Any function is allowed to use them, and any function may destroy them.
I've been doing this as well. It's something like "virtual registers". I use them mostly as pointers though, because I try really hard to avoid using temps in regular logic. I try to restrict myself to the registers, the stack, and the variables of the sub-system in question (like, if the purpose of a function is to output a value to a certain location, I might use that location for temporary storage before storing the final value there). The virtual register are my last resort.
User avatar
Banshaku
Posts: 2404
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Post by Banshaku »

blargg wrote:BTW, people here more often use labels prefixed with "@", which makes them "local" between the closest two non-@ labels. It's a less-formal scoping mechanism than ".proc".
In that case, if I do use .proc, using @ on the label inside the procedure is then redundant I guess?

By the way, I remember from one of the threads that you were posting on the 6502 forum (something about a warning for the compiler when you don't write # of something like that). Do they have a list of "best practices" for the 6502? I would love to see what I should and shouldn't do.

All those post recently made me learn all kind of interesting stuff for this platform. Learning every day :)
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

Banshaku wrote:By the way, I remember from one of the threads that you were posting on the 6502 forum (something about a warning for the compiler when you don't write # of something like that). Do they have a list of "best practices" for the 6502?
Forgetting the "#" is not a bad practice, it's an actual mistake! =)

It just happens to be the most common mistake one can make when programming the 6502, and it's a pretty nasty bug to find! "lda #$45" will put the number $45 into the accumulator, while "lda $45" will get a byte from memory location $45 and put that into the accumulator. Since we would hardly want to do that second thing (because we use names for the locations rather than the actual addresses) blargg suggested that load instructions without "#" be considered potential mistakes.
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

clueless wrote:ps - A google search for "makefile tutorial" will give you lots of useful tutorials. Most seem to be hosted on the personal web pages of college professors and TAs (teaching assistants in the USA) of various computer science departments.
And if you need a basic example of a makefile for an NROM game, look at the makefile for Tetramino.
clueless wrote:To deal with this, I have a set of generic "words" (tmp1_ptr ... tmp5_ptr) that I store in zero-page. Any function is allowed to use them, and any function may destroy them. If I need something in one preserved, then that caller must preserve it somehow.

I don't know if this is a good practice or not. It is kind of like a 'COMM' section from a really old main-frame program (I'm not that old... I just know enough about that stuff to sound dangerous).
Reserving $0000-$000F for scratch space is more like those old architectures that store their registers in RAM.
User avatar
clueless
Posts: 496
Joined: Sun Sep 07, 2008 7:27 am
Location: Seatlle, WA, USA

Post by clueless »

tepples wrote:Reserving $0000-$000F for scratch space is more like those old architectures that store their registers in RAM.
Ah yes, like the Intel 8051. I had forgotten. http://www.howtofriends.com/8051/, section 2.5
User avatar
clueless
Posts: 496
Joined: Sun Sep 07, 2008 7:27 am
Location: Seatlle, WA, USA

Post by clueless »

tepples wrote:And if you need a basic example of a makefile for an NROM game, look at the makefile for Tetramino.

I just looked at your project. I have a few questions, if you don't mind:

1) In your "nes.ini" file (ld65 linker control file), your "ROM" begins at $c000 but has a size of $8000. Is that a typo or is that by design?

2) Why does it appear that the iNES header is placed in the CPU address space at $7f00? I assume that since your game works this is doing what you want, but I don't understand how it gets linked properly.
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

clueless wrote:1) In your "nes.ini" file (ld65 linker control file), your "ROM" begins at $c000 but has a size of $8000. Is that a typo or is that by design?
Probably a bug. I originally planned an NROM-256 until I saw that I could do most of what I wanted in an NROM-128.
2) Why does it appear that the iNES header is placed in the CPU address space at $7f00?
I just put it anywhere that wouldn't overlap memory that the program is actually using, knowing that it wouldn't get loaded into the NES CPU's address space anyway.
Post Reply