Never Buff Enough

Working with text strings in C is the kind of 04:00 in the morning, keyboard smashing, programing purgatory most of us wouldn’t wish on our most anorak frenemy. No worries though right? You finally got your brilliant 50K creation compiled and ready to unleash upon the world.

Then it returns a week later in pieces, hacked apart at the seams. Evidently your personal take on Snake is to blame for shutting down the CIA, dumping Experian’s data coffers, and exposing Anna Kendrick’s Playboy negatives. Oops.

Should have sanitized that name input…

Buffer overflows are one of the oldest, most exploited, and on the surface truly asinine flaws in the computer industry today. Yet they stubbornly persist despite all efforts, begging the simple questions of how, and why?

This article, written by Perter Bright is one of the most comprehensive and well penned answers I believe you’ll find. Given the finicky nature of the net (Link rot for everyone!), a copy in full also appears below. I highly recommend you take a look.

 


How security flaws work: The buffer overflow

Starting with the 1988 Morris Worm, this flaw has bitten everyone from Linux to Windows.

The buffer overflow has long been a feature of the computer security landscape. In fact the first self-propagating Internet worm—1988’s Morris Worm—used a buffer overflow in the Unix finger daemon to spread from machine to machine. Twenty-seven years later, buffer overflows remain a source of problems. Windows infamously revamped its security focus after two buffer overflow-driven exploits in the early 2000s. And just this May, a buffer overflow found in a Linux driver left (potentially) millions of home and small office routers vulnerable to attack.

At its core, the buffer overflow is an astonishingly simple bug that results from a common practice. Computer programs frequently operate on chunks of data that are read from a file, from the network, or even from the keyboard. Programs allocate finite-sized blocks of memory—buffers—to store this data as they work on it. A buffer overflow happens when more data is written to or read from a buffer than the buffer can hold.

On the face of it, this sounds like a pretty foolish error. After all, the program knows how big the buffer is, so it should be simple to make sure that the program never tries to cram more into the buffer than it knows will fit. You’d be right to think that. Yet buffer overflows continue to happen, and the results are frequently a security catastrophe.

To understand why buffer overflows happen—and why their impact is so grave—we need to understand a little about how programs use memory and a little more about how programmers write their code. (Note that we’ll look primarily at the stack buffer overflow. It’s not the only kind of overflow issue, but it’s the classic, best-known kind.)

Stack it up

Buffer overflows create problems only for native code—that is, programs which use the processor’s instruction set directly rather than through some intermediate form such as in Java or Python. The overflows are tied to the way the processor and native code programs manipulate memory. Different operating systems have their own quirks, but every platform in common use today follows essentially the same pattern. To understand how these attacks work and some of the things people do to try to stop them, we first have to understand a little about how that memory is used.

The most important central concept is the memory address. Every individual byte of memory has a corresponding numeric address. When the processor loads and stores data from main memory (RAM), it uses the memory address of the location it wants to read and write from. System memory isn’t just used for data; it’s also used for the executable code that makes up our software. This means that every function of a running program also has an address.

In the early days of computing, processors and operating systems used physical memory addresses: each memory address corresponded directly to a particular piece of RAM. While some pieces of modern operating systems still have to use these physical memory addresses, all of today’s operating systems use a scheme called virtual memory.

With virtual memory, the direct correspondence between a memory address and a physical location in RAM is broken. Instead, software and the processor operate using virtual memory addresses. The operating system and processor together maintain a mapping between virtual memory addresses and physical memory addresses.

This virtualization enables a range of important features. The first and foremost is protected memory. Every individual process gets its own set of addresses. For a 32-bit process, those addresses start at zero (for the first byte) and run up to 4,294,967,295 (or in hexadecimal, 0xffff'ffff; 232 – 1). For a 64-bit process, they run all the way up to 18,446,744,073,709,551,615 (0xffff'ffff'ffff'ffff, 264 – 1). So, every process has its own address 0, its own address 1, its own address 2, and so on and so forth.

(For the remainder of this article, I’m going to stick to talking about 32-bit systems, except where otherwise noted. 32- and 64-bit systems work in essentially the same ways, so everything translates well enough; it’s just a little clearer to stick to one bitness.)

Because each process gets its own set of addresses, these scheme in a very straightforward way to prevent one process from damaging the memory of any other: all the addresses that a process can use reference memory belonging only to that process. It’s also much easier for the processes to deal with; physical memory addresses, while they broadly work in the same way (they’re just numbers that start at zero), tend to have wrinkles that make them annoying to use. For example, they’re usually not contiguous; address 0x1ff8'0000 is used for the processor’s System Management Mode memory; a small chunk of physical memory that’s off limits to normal software. Memory from PCIe cards also generally occupies some of this address space. Virtual addresses have none of these inconveniences.

So what does a process have in its address space? Broadly speaking, there are four common things, of which three interest us. The uninteresting one is, in most operating systems, “the operating system kernel.” For performance reasons, the address space is normally split into two halves, with the bottom half being used by the program and the top half being the kernel’s address space. The kernel-half of the memory is inaccessible to the program’s half, but the kernel itself can read the program’s memory. This is one of the ways that data is passed to kernel functions.

The first things that we need to care about are the executables and libraries that constitute the program. The main executable and all its libraries are all loaded into the process’ address space, and all of their constituent functions accordingly have memory addresses.

The second is the memory that the program uses for storing the data it’s working on, generally called the heap. This might be used, for example, to store the document currently being edited, the webpage (and all its JavaScript objects, CSS, and so on) being viewed, or the map for the game being played.

The third and most important is the call stack, generally just called the stack. This is the most complex aspect. Every thread in a process has its own stack. It’s a chunk of memory that’s used to keep track of both the function that a thread is currently running, as well as all the predecessor functions—the ones that were called to get to the current function. For example, if function a calls function b, and function b calls function c, then the stack will contain information about a, b, and c, in that order.

 

Here we see the basic layout of our stack with a 64 character buffer called <code>name</code>, then the frame pointer, and then the return address. <code>esp</code> has the address of the top of the stack, <code>ebp</code> has the address of the frame pointer.
Here we see the basic layout of our stack with a 64 character buffer called name, then the frame pointer, and then the return address. esp has the address of the top of the stack, ebp has the address of the frame pointer.

 

The call stack is a specialized version of the more general “stack” data structure. Stacks are variable-sized structures for storing objects. New objects can be added (“pushed”) to one end of the stack (conventionally known as the “top” of the stack), and objects can be removed (“popped”) from the stack. Only the top of the stack can be modified with a push or a pop, so the stack forces a kind of sequential ordering: the most recently pushed item is the one that gets popped first. The first item that gets pushed on the stack is the last one that gets popped.

The most important thing that the call stack does is to store return addresses. Most of the time, when a program calls a function, that function does whatever it is supposed to do (including calling other functions), and then returns to the function that called it. To go back to the calling function, there must be a record of what that calling function was: execution should resume from the instruction after the function call instruction. The address of this instruction is called the return address. The stack is used to maintain these return addresses: whenever a function is called, the return address is pushed onto the stack. Whenever a function returns, the return address is popped off the stack, and the processor begins executing the instruction at that address.

This stack functionality is so fundamentally important that most, if not all, processors include built-in support for these concepts. Consider x86 processors. Among the registers (small storage locations in the processor that can be directly accessed by processor instructions) that x86 defines, the two that are most important are eip, standing for “instruction pointer,” and esp, standing for stack pointer.

esp always contains the address of the top of the stack. Each time something is pushed onto the stack, the value in esp is decreased. Each time something is popped from the stack, the value of esp is increased. This means that the stack grows “down;” as more things are pushed onto the stack, the address stored in esp gets lower and lower. In spite of this, the memory location referenced by esp is still called the “top” of the stack.

eip gives the address of the currently executing instruction. The processor maintains eip itself. It reads the instruction stream from memory and increments eip accordingly so that it always has the instruction’s address. x86 has an instruction for function calls, named call, and another one for returning from a function, named ret.

call takes one operand; the address of the function to call (though there are several different ways that this can be provided). When a call is executed, the stack pointer esp is decremented by 4 bytes (32-bits), and the address of the instruction following the call, the return address, is written to the memory location now referenced by esp—in other words, the return address is pushed onto the stack. eip is then set to the address specified as operand to call, and execution continues from that address.

ret does the opposite. The simple ret doesn’t take any operands. The processor first reads the value from the memory address contained in esp, then increments esp by 4 bytes—it pops the return address from the stack. eip is set to this value, and execution continues from that address.

 

If the call stack only contained a sequence of return addresses, there wouldn’t be much scope for problems. The real problem comes with everything else that goes on the stack, too. The stack happens to be a quick and efficient place for storing data. Storing data on the heap is relatively complex; the program needs to keep track of how much space is available on the heap, how much space each piece of data is using, and various other bits of bookkeeping. But the stack is also simple; to make space for some data, just decrement the stack pointer. To tidy up when the data is no longer needed, increment the stack pointer.

This convenience makes the stack a logical place to store the variables that belong to a function. A function has a 256 byte buffer to read some user input? Easy, just subtract 256 from the stack pointer and you’ve created the buffer. At the end of the function, just add 256 back onto the stack pointer, and the buffer is discarded.

When we use the program correctly, the keyboard input is stored in the <code>name</code> buffer, followed by a null (zero) byte. The frame pointer and return address are unaltered.
When we use the program correctly, the keyboard input is stored in the name buffer, followed by a null (zero) byte. The frame pointer and return address are unaltered.

 

There are limitations to this. The stack isn’t a good place to store very large objects; the total amount of memory available is usually fixed when a thread is created, and that’s typically around 1MB in size. These large objects must be placed on the heap instead. The stack also isn’t usable for objects that need to exist for longer than the span of a single function call. Because every stack allocation is undone when a function exits, any objects that exist on the stack can only live as long as a function is running. Objects on the heap, however, have no such restriction; they can hang around forever.

This stack storage isn’t just used for the named variables that programmers explicitly create in their programs; it can also be used for storing whatever other values the program may need to store. This is traditionally a particularly acute concern on x86. x86 processors don’t have very many registers (there are only 8 integer registers in total, and some of those, like eip and esp, already have special purposes), and so functions can rarely keep all the values they need in registers. To free up space in a register while still ensuring that its current value can be retrieved later, the compiler will push the value of the register onto the stack. The value can then be popped later to put it back into a register. In compiler jargon, this process of saving registers so that they can be re-used is called spilling.

Finally, the stack is often used to pass arguments to functions. The calling function pushes each argument in turn onto the stack; the called function can then pop the arguments off. This isn’t the only way of passing arguments—they can be passed in registers too, for example—but it’s one of the most flexible.

The set of things that a function has on the stack—its local variables, its spilled registers, and any arguments it’s preparing to pass to another function—is called a “stack frame.” Because data within the stack frame is used so extensively, it’s useful to have a way of quickly referencing it.

The stack pointer can do this, but it’s somewhat awkward: the stack pointer always points to the top of the stack, and so it moves around as things are pushed and popped. For example, a variable may start out with an address of at esp + 4. Two more values might be pushed onto the stack, meaning that the variable now has to be accessed at esp + 12. One of those values can then get popped off, so the variable is now at esp + 8.

This isn’t an insurmountable difficulty, and compilers can easily handle the challenge. Still, it can make using the stack pointer to access anything other than “the top of the stack” awkward, especially for the hand-coded assembler.

To make things easier, it’s common to maintain a second pointer, one that consistently stores the address of the bottom (start) of each stack frame—a value known as the frame pointer—and on x86, there’s even a register that’s generally used to store this value, ebp. Since this never changes within a given function, this provides a consistent way to access a function’s variables: a value that’s at ebp - 4 will remain at ebp - 4 for the whole of a function. This isn’t just useful for humans; it also makes it easier for debuggers to figure out what’s going on.

This screenshot from Visual Studio shows some of this in action for a simple x86 program. On x86 processors, the register named <code>esp</code> contains the address of the top stack, in this case <code>0x0019fee0</code>, highlighted in blue (on x86, the stack actually grows downwards, toward memory address <code>0</code>, but it's still called the top of the stack anyway). This function only has one stack variable, <code>name</code>, highlighted in pink. It's a fixed size 32-byte buffer. Because it's the only variable, its address is also <code>0x0019fee0</code>, the same as the top of the stack.</p> <p>x86 also has a register called <code>ebp</code>, highlighted in red, that's (normally) dedicated to storing the location of the frame pointer. The frame pointer is placed immediately after the stack variables. Right after the frame pointer is the return address, highlighted in green. The return address references a code fragment with address <code>0x00401048</code>. This instruction comes immediately after a <code>call</code> instruction, making clear the way the return address is used to resume execution from where the calling function left off.

This screenshot from Visual Studio shows some of this in action for a simple x86 program. On x86 processors, the register named esp contains the address of the top stack, in this case 0x0019fee0, highlighted in blue (on x86, the stack actually grows downwards, toward memory address 0, but it’s still called the top of the stack anyway). This function only has one stack variable, name, highlighted in pink. It’s a fixed size 32-byte buffer. Because it’s the only variable, its address is also 0x0019fee0, the same as the top of the stack.x86 also has a register called ebp, highlighted in red, that’s (normally) dedicated to storing the location of the frame pointer. The frame pointer is placed immediately after the stack variables. Right after the frame pointer is the return address, highlighted in green. The return address references a code fragment with address 0x00401048. This instruction comes immediately after a call instruction, making clear the way the return address is used to resume execution from where the calling function left off.

</p> <p>Unfortunately <code>gets()</code> is a really stupid function. If we just hold down A on the keyboard it won't stop once it's filled the <code>name</code> buffer. It'll just keep on writing data to memory, overwriting the frame pointer, the return address, and anything and everything else it can.

Unfortunately gets() is a really stupid function. If we just hold down A on the keyboard it won’t stop once it’s filled the name buffer. It’ll just keep on writing data to memory, overwriting the frame pointer, the return address, and anything and everything else it can.

 

name in the above screenshot is the kind of buffer that’s regularly overflowed. Its size is fixed at exactly 64 characters. In this case it’s filled with a bunch of numbers, and it ends in a final null. As should be clear from the above picture, if more than 64 bytes are written into the name buffer, then other values on the stack will be damaged. If four extra bytes are written, the frame pointer will be destroyed. If eight extra bytes are written, both the frame pointer and the return address get overwritten.

Clearly this will lead to damaging the program’s data, but the problem of buffer flows is more serious: they often lead to code execution. This happens because those overflowed buffers won’t just overwrite data. They can also overwrite the other important thing kept on the stack—those return addresses. The return address controls which instructions the processor will execute when it’s finished with the current function; it’s meant to be some location within the calling function, but if it gets overwritten in a buffer overflow, it could point anywhere. If attackers can control the buffer overflow, they can control the return address; if they can control the return address, they can choose what code the processor executes next.

The process probably won’t have some nice, convenient “compromise the machine” function for the attacker to run, but that doesn’t really matter. The same buffer that was used to overwrite the return address can also be used to hold a short snippet of executable code, called shellcode, that will in turn download a malicious executable, or open up a network connection, or do whatever else the attacker fancies.

Traditionally, this was trivial to do because of a trait that may seem a little surprising: generally, each program would use the same memory addresses each time you ran it, even if you rebooted in between. This means that the location of the buffer on the stack would be the same each time, and so the value used to overwrite the return address could be the same each time. An attacker only had to figure out what the address was once, and the attack would work on any computer running the flawed code.

An attacker’s toolkit

In an ideal world—for the attacker, that is—the overwritten return address can simply be the address of the buffer. When the program is reading input from a file or a network, this can often be the case for example.

Other times the attacker has to employ tricks. In functions that process human-readable text, the zero byte (or “null”) is often treated specially; it indicates the end of a string, and the functions used for manipulating strings—copying them, comparing them, combining them—will stop whenever they hit the null character. This means that if the shellcode contains the null character, those routines are liable to break it.

 

To exploit the overflow, instead of just writing As and smashing everything, the attacker fills the buffer with shellcode: a short piece of executable code that will perform some action of the attacker's choosing. The return address is then overwritten with an address referring to the buffer, directing the processor to execute the shellcode when it tries to return from a function call.
To exploit the overflow, instead of just writing As and smashing everything, the attacker fills the buffer with shellcode: a short piece of executable code that will perform some action of the attacker’s choosing. The return address is then overwritten with an address referring to the buffer, directing the processor to execute the shellcode when it tries to return from a function call.

 

To handle this, attackers can use various techniques. Pieces of code can convert shellcode that contains null characters into equivalent sequences that avoid the problem byte. They can even handle quite strict restrictions; for example, an exploitable function may only accept input that can be typed on a standard keyboard.

The address of the stack itself often contains a null byte, which is similarly problematic: it means that the return address cannot be directly set to the address of the stack buffer. Sometimes this isn’t a big issue, because some of the functions that are used to fill (and, potentially, overflow) buffers will write a null byte themselves. With some care, they can be used to put the null byte in just the right place to set the return address to that of the stack.

Even when that isn’t possible, this situation can be handled with indirection. The program and all its libraries mean that memory is littered with executable code. Much of this executable code will have an address that’s “safe,” which is to say has no null bytes.

What the attacker has to do is find a usable address that contains an instruction such as x86’s call esp, which treats the value of the stack pointer as the address of a function and begins executing it—a perfect match for a stack buffer that contains the shellcode. The attacker then uses the address of the call esp instruction to overwrite the return address; the processor will take an extra hop through this address but still end up running the shellcode. This technique of bouncing through another address is called “trampolining.”

 

Sometimes it can be difficult to overwrite the return address with the address of the buffer. To handle this, we can overwrite the return address with the address of a piece of executable code found within the victim program (or its libraries). This fragment of code will transfer execution to the buffer for us.
Sometimes it can be difficult to overwrite the return address with the address of the buffer. To handle this, we can overwrite the return address with the address of a piece of executable code found within the victim program (or its libraries). This fragment of code will transfer execution to the buffer for us.

 

This works because, again, the program and all its libraries occupy the same memory addresses every time they run—even across reboots and even across different machines. One of the interesting things about this is that the library that provides the trampoline does not need to ever perform a call esp itself. It just needs to offer the two bytes (in this case 0xff and 0xd4) adjacent to each other. They could be part of some other instruction or even a literal number; x86 isn’t very picky about this kind of thing. x86 instructions can be very long (up to 15 bytes!) and can be located at any address. If the processor starts reading an instruction from the middle—from the second byte of a four byte instruction, say—the result can often be interpreted as a completely different, but still valid, instruction. This can make it quite easy to find useful trampolines.

Sometimes, however, the attack can’t set the return address to exactly where it needs to go. Although the memory layout is very similar, it might vary slightly from machine to machine or run to run. For example, the precise location of an exploitable buffer might vary back and forth by a few bytes depending on the system’s name or IP address, or because a minor update to the software has made a very small change. To handle this, it’s useful to be able to specify a return address that’s roughly correct but doesn’t have to be exactly correct.

This can be handled easily through a technique called the “NOP sled.” Instead of writing the shellcode directly into the buffer, the attacker writes a large number of “NOP” instructions (meaning “no-op”; they’re instructions that don’t actually do anything), sometimes hundreds of them, before the real shellcode. To run the shellcode, the attacker only needs to set the return address to somewhere among these NOP instructions. As long as they land within the NOPs, the processor will quickly run through them until it reaches the real shellcode.

Blame C

The core bug that enables these attacks, writing more to a buffer than the buffer has space for, sounds like something that should be simple to avoid. It’s an exaggeration (but only a slight one) to lay the blame entirely on the C programming language and its more or less compatible offshoots, namely C++ and Objective-C. The C language is old, widely used, and essential to our operating systems and software. It’s also appallingly designed, and while all these bugs are avoidable, C does its damnedest to trip up the unwary.

As an example of C’s utter hostility to safe development, consider the function gets(). The gets() function takes one parameter—a buffer—and reads a line of data from standard input (which normally means “the keyboard”), then puts it into the buffer. The observant may have noticed that gets() doesn’t include a parameter for the buffer’s size, and as an amusing quirk of C’s design, there’s no way for gets() to figure out the buffer’s size for itself. And that’s because gets() just doesn’t care: it will read from standard input until the person at the keyboard presses return, then try to cram everything into the buffer, even if the person typed far more than the buffer could ever contain.

This is a function that literally cannot be used safely. Since there’s no way of constraining the amount of text typed at the keyboard, there’s no way of preventing gets() from overflowing the buffer it is passed. The creators of the C standard did soon realize the problem; the 1999 revision to the C specification deprecated gets(), while the 2011 update removed it entirely. But its existence—and occasional usage—is a nice indication of the kind of traps that C will spring on its users.

The Morris worm, the first self-replicating malware that spread across the early Internet in a couple of days in 1988, exploited this function. The BSD 4.3 fingerd program listened for network connections on port 79, the finger port. finger is an ancient Unix program and corresponding network protocol used to see who’s logged in to a remote system. It can be used in two ways; a remote system can be queried to see everyone currently logged in. Alternatively, it can be queried about a specific username, and it will tell you some information about that user.

Whenever a connection was made to the finger daemon, it would read from the network—using gets()—into a 512 byte buffer on the stack. In normal operation, fingerd would then spawn the finger program, passing it the username if there was one. The finger program was the one that did the real work of listing users or providing information about any specific user. fingerd was simply responsible for listening to the network and starting finger appropriately.

Given that the only “real” parameter is a possible username, 512 bytes is plainly a huge buffer. Nobody is likely to have a username anything like that long. But no part of the system actually enforced that constraint because of the use of the awful gets() function. Send more than 512 bytes over the network and fingerd would overflow its buffer. So this is exactly what Robert Morris did: his exploit sent 537 bytes to fingerd (536 bytes of data plus a new-line character, which made gets() stop reading input), overflowing the buffer and overwriting the return address. The return address was set simply to the address of the buffer on the stack.

The Morris worm’s executable payload was simple. It started with 400 NOP instructions, just in case the stack layout was slightly different, followed by a short piece of code. This code spawned the shell, /bin/sh. This is a common choice of attack payload; the fingerd program ran as root, so when it was attacked to run a shell, that shell also ran as root. fingerd was plumbed into the network, taking its “keyboard input” from the network and likewise sending its output back over the network. Both of these features are inherited by the shell executed by the exploit, meaning that the root shell was now usable remotely by the attacker.

While gets() is easy to avoid—in fact, even at the time of the Morris worm, a fixed version of fingerd that didn’t use gets() was available—other parts of C are harder to ignore and no less prone to screw ups. C’s handling of text strings is a common cause of problems. The behavior mentioned previously—stopping at null bytes—comes from C’s string behavior. In C, a string is a sequence of characters, followed by a null byte to terminate the string. C has a range of functions for manipulating these strings. Perhaps the best pair are strcpy(), which copies a string from a source to a destination, and strcat(), which appends a source string to a destination. Neither of these functions has a parameter for the size of the destination buffer. Both will merrily read from their source forever until they reach a null character, filling up the destination and overflowing it without a care in the world.

Even when C’s string handling functions do take a parameter for the buffer size, they can do so in a way that leads to errors and overflows. C offers a pair of siblings to strcat() and strcpy() called strncat() and strncpy(). The extra n in their names denotes that they take a size parameter, of sorts. But n is not, as many naive C programmers believe, the size of the buffer being written to; it is the number of characters from the source to copy. If the source runs out of characters (because a null byte is reached) then strncpy() and strncat() will make up the difference by copying more null bytes to the destination. At no point do the functions ever care about the actual size of the destination.

Unlike gets(), it is possible to use these functions safely; it’s just difficult. C++ and Objective-C both include superior alternatives to C’s functions, making string manipulation much simpler and safer, but they retain the old C capabilities for reasons of backwards compatibility.

Moreover, they retain C’s fundamental weakness: buffers do not know their own size, and the language never validates the reads and writes performed on buffers, allowing them to overflow. This same behavior also led to the recent Heartbleed bug in OpenSSL. That wasn’t an overflow; it was an overread; the C code in OpenSSL tried to read more from a buffer than the buffer contained, leaking sensitive information to the world.

Fixing the leaks

Needless to say, it is not beyond the wit of mankind to develop languages in which reads from and writes to buffers are validated and so can never overflow. Compiled languages such as the Mozilla-backed Rust, safe runtime environments such as Java and .NET, and virtually every scripting language like Python, JavaScript, Lua, Python, and Perl are immune to this problem (although .NET does allow developers to explicitly turn off all the safeguards and open themselves up to this kind of bug once more should they so choose).

That the buffer overflow continues to be a feature of the security landscape is a testament to C’s enduring appeal. This is in no small part due to the significant issue of legacy code. An awful lot of C code still exists, including the kernel of every major operating system and popular libraries such as OpenSSL. Even if developers want to use a safe language such as C#, they may need to depend on a third-party library written in C.

Performance arguments are another reason for C’s continued use, though the wisdom of this approach was always a little unclear. It’s true that compiled C and C++ tend to produce fast executables, and in some situations that matters a great deal. But many of us have processors that spend the vast majority of their time idling; if we could sacrifice, say, ten percent of the performance of our browsers in order to get a cast iron guarantee that buffer overflows—in addition to many other common flaws—were impossible, we might decide that would be a fair trade-off, if only someone were willing to create such a browser.

Nonetheless, C and its friends are here to stay; as such, so are buffer overflows.

Some effort is made to stop the overflow errors before they bite anyone. During development there are tools that can analyze source code and running programs to try to detect dangerous constructs or overflow errors before those bugs ever make their way into shipping software. New tools such as AddressSantizer and older ones such as Valgrind both offer this kind of capability.

However, these tools both require the active involvement of the developer, meaning not all programs use them. Systemic protections that strive to make buffer overflows less dangerous when they do occur can protect a much greater variety of software. In recognition of this, operating system and compiler developers have implemented a number of systems to make exploiting these overflows harder.

Some of these systems are intended to make specific attacker tasks harder. One set of Linux patches made sure that system libraries were all loaded at low addresses to ensure that they contained at least one null byte in their address; this makes it harder to use their addresses in any overflow that uses C string handling.

Other defenses are more general. Many compilers today have some kind of stack protection. A runtime-determined value known as a “canary” is written onto the end of the stack near where the return address is stored. At the end of every function, that value is checked for modification before the return instruction is issued. If the canary value has changed (because it has been overwritten in a buffer overflow) then the program will immediately crash rather than continue.

Perhaps the most important single protection is one variously known as W^X (“write exclusive-or execute”), DEP (“data execution prevention”), NX (“No Xecute”), XD (“eXecute Disable”), EVP (“Enhanced Virus Protection,” a rather peculiar term sometimes used by AMD), XN (“eXecute Never”), and probably more. The principle here is simple. These systems strive to make memory either writeable (suitable for buffers) or executable (suitable for libraries and program code) but not both. Thus, even if an attacker can overflow a buffer and control the return address, the processor will ultimately refuse to execute the shellcode.

Whichever name you use, this is an important technique not least because it comes at essentially no cost. This approach leverages protective measures built into the processor itself as part of the hardware support for virtual memory.

As described before, with virtual memory every process gets its own set of private memory addresses. The operating system and processor together maintain a mapping from virtual addresses to something else; sometimes a virtual address corresponds to a physical memory address, sometimes it corresponds to a portion of a file on disk, and sometimes it corresponds to nothing at all because it has not been allocated. This mapping is granular, typically using 4,096 byte chunks called pages.

The data structures used to store the mapping don’t just include the location (physical memory, disk, nowhere) of each page; they also contain (usually) three bits defining the page’s protection: whether the page is readable, whether it is writeable, and whether it is executable. With this protection, areas of the process’ memory that are used for data, such as the stack, can be marked as readable and writeable but not executable. Conversely, areas such as the program’s executable code and libraries can be marked as readable and executable but not writeable.

One of the great things about NX is that it can be applied to existing programs retroactively just by updating the operating system to one that supports it. Occasionally programs do run into problems. Just-in-time compilers, used for things like Java and .NET, generate executable code in memory at runtime, and as such need memory that is both writeable and executable (though strictly, they don’t need it to be both things simultaneously). In the days before NX, any memory that was readable was also executable, so these JIT compilers never had to do anything special to their read-writeable buffers. With NX, they need to make sure to change the memory protection from read-write to read-execute.

The need for something like NX was clear, especially for Microsoft. In the early 2000s, a pair of worms showed that the company had some serious code security problems: Code Red, which infected as many as 359,000 Windows 2000 systems running Microsoft’s IIS Web server in July 2001, and SQL Slammer, which infected more than 75,000 systems running Microsoft’s SQL Server database in January 2003. These were high-profile embarrassments.

Both of them exploited stack buffer overflows, and strikingly, though they came 13 and 15 years after the Morris worm, the method of exploitation was virtually identical. An exploit payload was placed into the buffer on the stack and the return address overwritten to execute it. (The only slight nuance was that both of these used the trampoline technique. Instead of setting the return address directly to the address of the stack, they set the return address to an instruction that in turn passes execution to the stack.)

Naturally, these worms were also advanced in other ways. Code Red’s payload didn’t just self-replicate; it also defaced webpages and attempted to perform denial of service attacks. SQL Slammer packed everything it needed to find new machines to exploit and spread through a network in just a few hundred bytes, and it left no footprint on machines it infected; reboot and it was gone. Both worms also worked on an Internet that was enormously larger than the one the Morris worm worked with, and hence they infected many more machines.

But the central issue, that of a straightforwardly exploitable stack buffer overflow, was an old one. These worms were both major news and made many people question the use of Windows in any kind of an Internet-facing, server capacity. Microsoft’s response was to start taking security seriously. Windows XP Service Pack 2 was the first real product with this mindset. It utilized a number of software changes, including a software firewall, changes to Internet Explorer to prevent silent installation of toolbars, plugins—and NX support.

Hardware supporting NX has been mainstream since 2004, when Intel introduced the Prescott Pentium 4, and operating system support for NX has been widespread since Windows XP Service Pack 2. Windows 8 forced the issue even more by cutting off support for older processors that didn’t have NX hardware.

Beyond NX

In spite of the spread of NX support, buffer overflows remain a security issue to this day. That’s because a number of techniques were devised to bypass NX.

The first of these was similar to the trampolining trick already described to pass control to the shellcode in a stack buffer via an instruction found in another library or executable. Instead of looking for a fragment of executable code that will pass execution directly back to the stack, the attacker looks for a fragment that does something useful in its own right.

Perhaps the best candidate for this is the Unix system() function. system() takes one parameter: the address of a string representing a command line to be executed, and traditionally that parameter is passed on the stack. The attacker can create a command-line string and put it in the buffer to be overflowed, and because (traditionally) things didn’t move around in memory, the address of that string would be known and could be put on the stack as part of the attack. The overwritten return address in this situation isn’t set to the address of the buffer; it’s set to the address of the system() function. When the function with the buffer overflow finishes, instead of returning to its caller, it runs the system() function to execute a command of the attacker’s choosing.

This neatly bypasses NX. The system() function, being part of a system library, is already executable. The exploit doesn’t have to execute code from the stack; it just has to read the command line from the stack. This technique is called “return-to-libc” and was invented in 1997 by Russian computer security expert Solar Designer. (libc is the name of the Unix library that implements many key functions, including system(), and is typically found loaded into every single Unix process, so it makes a good target for this kind of thing.)

While useful, this technique can be somewhat limited. Often functions don’t take their arguments from the stack; they expect them to be passed in registers. Passing in command-line strings to execute is nice, but it often involves those annoying nulls, which can foul everything up. Moreover, it makes chaining multiple function calls very difficult. It can be done—provide multiple return addresses instead of one—but there’s no provision for changing the order of arguments, using return values, or anything else.

</p> <p>Instead of filling the buffer with shellcode, we fill it with a sequence of return addresses and data. These return addresses pass control to existing fragments of executable code within the victim program and its libraries. Each fragment of code performs an operation and then returns, passing control to the next return address.

Instead of filling the buffer with shellcode, we fill it with a sequence of return addresses and data. These return addresses pass control to existing fragments of executable code within the victim program and its libraries. Each fragment of code performs an operation and then returns, passing control to the next return address.

 

Over the years, return-to-libc was generalized to alleviate these restrictions. In late 2001, a number of ways to extend return-to-libc to make multiple function calls was documented, along with solutions for the null byte problem. These techniques were nonetheless limited. A more complicated technique formally described in 2007 for the most part lifted all these restrictions: return-oriented-programming (ROP).

This used the same principle as from return-to-libc and trampolining but generalized further still. Where trampolining uses a single fragment of code to pass execution to shellcode in a buffer, ROP uses lots of fragments of code, called “gadgets” in the original ROP paper. Each gadget follows a particular pattern: it performs some operation (putting a value in a register, writing to memory, adding two registers, etc.) followed by a return instruction. The same property that makes x86 good for trampolining works here too; the system libraries loaded into a process contain many hundreds of sequences that can be interpreted as “perform an action, then return” and hence can be used in ROP-based attacks.

The gadgets are all chained together by a long sequence of return addresses (and any useful or necessary data) written to the stack as part of the buffer overflow. The return instructions leap from gadget to gadget with the processor rarely or never calling functions, only ever returning from them. Remarkably, it was discovered that, at least on x86, the number and variety of useful gadgets is such that an attacker can generally do anything; this weird subset of x86, used in a peculiar way, is often Turing complete (though the exact range of capabilities will depend on which libraries a given program has loaded and hence which gadgets are available).

As with return-to-libc, all the actual executable code is taken from system libraries, and so NX protection is useless. The greater flexibility of the approach means that exploits can do the things that are difficult even with chained return-to-libc, such as calling functions that take arguments in registers, using return values from one function as an argument for another, and much more besides.

The ROP payloads vary. Sometimes they’re simple “create a shell”-style code. Another common option is to use ROP to call a system function to change the NX state of a page of memory, flipping it from being writable to being executable. Doing this, an attacker can use a conventional, non-ROP payload, using ROP only to make the non-ROP payload executable.

Getting random

This weakness of NX has long been recognized, and a recurring theme runs throughout all these exploits: the attacker knows the memory addresses of the stack and system libraries ahead of time. Everything is contingent on this knowledge, so an obvious thing to try is removing that knowledge. This is what Address Space Layout Randomization (ASLR) does: it randomizes the position of the stack and the in-memory location of libraries and executables. Typically these will change either every time a program is run, every time a system is booted, or some combination of the two.

This greatly increases the difficulty of exploitation, because all of a sudden, the attacker doesn’t know where the ROP instruction fragments will be in memory, or even where the overflowed stack buffer will be.

ASLR in many ways goes hand in hand with NX, because it shores up the big return-to-libc and return-oriented-programming gaps that NX leaves. Unfortunately, it’s a bit more intrusive than NX. Except for JIT compilers and a few other unusual things, NX could be safely added to existing programs. ASLR is more problematic; programs and libraries need to ensure that they do not make any assumptions about the address they’re loaded at.

On Windows, for example, this shouldn’t be a huge issue for DLLs. DLLs on Windows have always supported being loaded at different addresses, but it could be an issue for EXEs. Before ASLR, EXEs would always be loaded at an address of 0x0040000 and could safely make assumptions on that basis. After ASLR, that’s no longer the case. To make sure that there won’t be any problems, Windows by default requires executables to indicate that they specifically support ASLR and opt in to enabling it. The security conscious can, however, force Windows to enable it for all executables and libraries even if programs don’t indicate that they support it. This is almost always fine.

The situation is perhaps worse on x86 Linux, as the approach used for ASLR on that platform exacts a performance cost that may be as high as 26 percent. Moreover, this approach absolutely requires executables and libraries to be compiled with ASLR support. There’s no way for an administrator to mandate the use of ASLR as there is in Windows. (x64 does not quite eliminate the performance cost of the Linux approach, but it does greatly alleviate it.)

When ASLR is enabled, it provides a great deal of protection against easy exploitation. ASLR still isn’t perfect, however. For example, one restriction is the amount of randomness it can provide. This is especially acute on 32-bit systems. Although the memory space has more than 4 billion different addresses, not all of those addresses are available for loading libraries or placing the stack.

Instead, it’s subject to various constraints. Some of these are broad goals. Generally, the operating system likes to keep libraries loaded fairly close together at one end of the process’ address space, so that as much contiguous empty space is available to the application as possible. You wouldn’t want to have one library loaded every 256MB throughout the memory space, because the biggest single allocation you’d be able to make would be a little less than 256MB, which limits the ability of applications to work on big datasets.

Executables and libraries generally have to be loaded so that they start on, at the very least, a page boundary. Normally, this means they must be loaded at an address that’s a multiple of 4,096. Platforms can have similar conventions for the stack; Linux, for example, starts the stack on a multiple of 16 bytes. Systems under memory stress sometimes have to further reduce the randomness in order to fit everything in.

The impact of this varies, but it means that attackers can sometimes guess what an address will be and have a reasonable probability of guessing right. Even a fairly low chance—one in 256, say—can be enough in some situations. When attacking a Web server that will automatically restart crashed processes, it may not matter that 255 out of 256 attacks crash the server. It will simply be restarted, and the attacker can try again.

But on 64-bit systems, there’s so much address space that this kind of guessing approach is untenable. The attacker could be stuck with a one in a million or one in a billion chance of getting it right, and that’s a small enough chance as to not matter.

Guessing and crashing isn’t much good for attacks on, say, browsers; no user is going to restart a browser 256 times in a row just so that an attacker can strike it lucky. As a result, exploiting this kind of flaw on a system with both NX and ASLR can’t be done without help.

This help can come in many forms. One route in browsers is to use JavaScript or Flash—both of which contain JIT compilers that generate executable code—to fill large portions of memory with carefully constructed executable code. This produces a kind of large-scale NOP sled in a technique known as “heap spraying.” Another approach is to find a secondary bug that inadvertently reveals memory addresses of libraries or of the stack, giving the attacker enough information to construct a custom set of ROP return addresses.

A third approach was again common in browsers: take advantage of libraries that don’t use ASLR. Old versions of, for example, Adobe’s PDF plugin or Microsoft’s Office browser plugins didn’t enable ASLR, and Windows by default doesn’t force ASLR on non-ASLR code. If attackers could force such a library to load (by, for example, loading a PDF into a hidden browser frame) then they no longer needed to be too concerned about ASLR; they could just use that non-ASLR library for their ROP payload.

A never-ending war

The world of exploitation and mitigation techniques is one of cat and mouse. Powerful protective systems such as ASLR and NX raise the bar for taking advantage of flaws and together have put the days of the simple stack buffer overflow behind us, but smart attackers can still combine multiple flaws to defeat these protections.

The escalation continues. Microsoft’s EMET (“Enhanced Mitigation Experience Toolkit”) includes a range of semi-experimental protections that try to detect heap spraying or attempts to call certain critical functions in ROP-based exploits. But in the continuing digital arms war, even these have security techniques that have been defeated. This doesn’t make them useless—the difficulty (and hence cost) of exploiting flaws goes up with each new mitigation technique—but it’s a reminder of the need for constant vigilance.

Thanks to Melissa Elliott for her invaluable feedback.

Dynamic Stored Procedure Call Generation

PHP

Scripting experiment. Inspector Blair page layout is currently static code. I would like to upgrade to a content managed layout. One of the first challenges is overcoming the diverse array of forms and subsequent data calls. Dynamic queries are NOT acceptable. Instead I’ll need to find a way to create dynamic inputs for stored procedures that can be driven from a forms database.

This experiment was to test a simple array generator. Normally saving to the database is handled by a set of class calls as in this excerpt:

case RECORD_NAV_COMMANDS::SAVE:
	
	// Stop errors in case someone tries a direct command link.
	if($obj_navigation_rec->get_command() != RECORD_NAV_COMMANDS::SAVE) break;
							
	// Save the record. Saving main record is straight forward. We’ll run the populate method on our 
	// main data object which will gather up post values. Then we can run a query to merge the values into 
	// database table. We’ll then get the id from saved record (since we are using a surrogate key, the ID
	// should remain static unless this is a brand new record). 
	
	// If necessary we will then save any sub records (see each for details).
	
	// Finally, we redirect to the current page using the freshly acquired id. That will ensure we have 
	// always an up to date ID for our forms and navigation system.			

	// Populate the object from post values.			
	$_main_data->populate_from_request();
	
	// --Sub data: Role.
	$_obj_data_sub_request = new class_account_role_data();
	$_obj_data_sub_request->populate_from_request();

	// Let's get account info from the active directory system. We'll need to put
	// names int our own database so we can control ordering of output.
	$account_lookup = new class_access_lookup();
	$account_lookup->lookup($_main_data->get_account());

	// Call update stored procedure.
	$query->set_sql('{call account_update(@id			= ?,
											@log_update_by	= ?, 
											@log_update_ip 	= ?,										 
											@account 		= ?,
											@department 	= ?,
											@details		= ?,
											@name_f			= ?,
											@name_l			= ?,
											@name_m			= ?,
											@sub_role_xml	= ?)}');
											
	$params = array(array('<root><row id="'.$_main_data->get_id().'"/></root>', 		SQLSRV_PARAM_IN),
				array($access_obj->get_id(), 				SQLSRV_PARAM_IN),
				array($access_obj->get_ip(), 			SQLSRV_PARAM_IN),
				array($_main_data->get_account(), 		SQLSRV_PARAM_IN),						
				array($_main_data->get_department(),	SQLSRV_PARAM_IN),						
				array($_main_data->get_details(), 		SQLSRV_PARAM_IN),
				array($account_lookup->get_account_data()->get_name_f(), SQLSRV_PARAM_IN),
				array($account_lookup->get_account_data()->get_name_l(), SQLSRV_PARAM_IN),
				array($account_lookup->get_account_data()->get_name_m(), SQLSRV_PARAM_IN),
				array($_obj_data_sub_request->xml(), 	SQLSRV_PARAM_IN));
	
	//var_dump($params);
	//exit;
	
	$query->set_params($params);			
	$query->query();
	
	// Repopulate main data object with results from merge query.
	$query->get_line_params()->set_class_name('blair_class_account_data');
	$_main_data = $query->get_line_object();
	
	// Now that save operation has completed, reload page using ID from
	// database. This ensures the ID is always up to date, even with a new
	// or copied record.
	header('Location: '.$_SERVER['PHP_SELF'].'?id='.$_main_data->get_id());
	
	break;

 

Before we can begin to control the above calls dynamically, we’ll need to break the call down and see if we can assemble the sql string. Here we will concentrate on building the SQL string.

The form parts and column names they send data too will likely be stored in a sub-table of the forms database, and output as a linked list. We need to use those column names in a call string for sending or retrieving data. This simple experiment uses a keyed array to simulate the list we might get and see if we can concatenate a usable stored procedure call string.

$_main_data->populate_from_request();
            
// --Sub data: Role.
$_obj_data_sub_request = new class_account_role_data();
$_obj_data_sub_request->populate_from_request();

// Let's get account info from the active directory system. We'll need to put
// names int our own database so we can control ordering of output.
$account_lookup = new class_access_lookup();
$account_lookup->lookup($_main_data->get_account());

$save_row['id']                = '<root><row id="'.$_main_data->get_id().'"/></root>';
$save_row['log_update_by']    = $access_obj->get_id();
$save_row['log_update_ip']     = $access_obj->get_ip();
$save_row['account']         = $_main_data->get_account();
$save_row['department']        = $_main_data->get_department();
$save_row['name_f']         = $account_lookup->get_account_data()->get_name_f();
$save_row['name_l']         = $account_lookup->get_account_data()->get_name_l();
$save_row['name_m']         = $account_lookup->get_account_data()->get_name_m();    
$save_row['sub_role_xml']     = $_obj_data_sub_request->xml();        

$sql_str = '{call account_update(@';
$sql_str .= implode(' = ?, @', array_keys($save_row));
$sql_str .= ')}';
echo $sql_str;

//

Obviously this alone won’t be enough, but the resulting output looks quite promising:

{call account_update(@id = ?, @log_update_by = ?, @log_update_ip = ?, @account = ?, @department = ?, @name_f = ?, @name_l = ?, @name_m = ?, @sub_role_xml)}

 

 

Bootstrap Remote File Models

Introduction

Opening a model is well documented for the bootstrap framework, assuming the model’s contents are located within the same document. However, if the model target is a remote file the process becomes slightly more nebulous. After some experimenting and Stack Overflow research I have compiled the following steps to do so.

Link

The link for opening a model must still target a container element of some sort, typically a DIV id. You may also choose to target the container by class. For remote files, you need to include a href as well, targeting the remote file location.

<a href="model_document.php" data-target="#model_div_container_id" data-toggle="modal">Open Model</a>

Model Container

Next you need to prepare the calling document (the same document that contains model link) with a model container. This is exactly the same as with a normal model, except you are only adding the model’s container elements, not its content.

<div class="modal fade" id="#model_div_container_id">
    <div class="modal-dialog">
        <div class="modal-content">
        	<!-- Model content area - leave this empty.-->
        </div>
    </div>
</div>

Model Document (Content)

The last step is to prepare your remote document. Add whatever content you wish. The only special caveat to consider is you do NOT add the model container elements to this document. You only add the content. The content itself may include its own container elements, formatting, and so forth. In the example below, the model is a simple list, and therefore only the markup for the list is included.

<ul>	
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</ul>

You’ve probably already guessed what’s going on here. When the model link is clicked, the remote document’s contents are inserted into the calling document’s target element. Bootstrap Model Documentation (extracted 2017-01-23):

If a remote URL is provided, content will be loaded one time via jQuery’s load method and injected into the .modal-content div. If you’re using the data-api, you may alternatively use the href attribute to specify the remote source.

This is why you must include a target element in the calling document, and conversely NOT include model containers in the remote document. Hope this helps. Special thanks to Buzinas from Stack Overflow.

Until next time!

DC

Class – Utility

<?php 

class class_utility {

	/*
	Utility
	Damon Vaughn Caskey
	2013-01-09
	
	Miscellaneous utility functions.
	*/
	
	public function utl_color_alternation($i, $cColorEven="#DDDDFF", $cColorOdd="#CECEFF")
	{		
		/*
		back_alt_0001 - https://www.caskeys.com/dc/?p=4900
		Damon Vaughn Caskey
		2012-10-18
		
		Output alternating background colors for even/odd rows in tables or other types of layouts.
		
		$i:				Current row/location/count. 
		$cColorEven:	Color to output if $i is an even number.
		$cColorEven:	Color to output if $i is an odd number.
		*/
		
		if($i%2)				//Even number?
		{				
			return $cColorEven;	//Return even color.
		}
		else
		{	
			return $cColorOdd;	//Return odd color.
		}
	}
	
	public function utl_get_get($cID, $cDefault=NULL)
	{
		/*
		utl_get_get
		Damon Vaughn Caskey
		2013-01-01
		
		Wrapper to obtain get value.
		
		$cID:		Index.
		$cDefault:	Default on not set.
		*/
		
		return $this->utl_validate_isset($_GET[$cID], $cDefault);
	}
	
	public function utl_get_post($cID, $cDefault=NULL)
	{
		/*
		utl_get_post
		Damon Vaughn Caskey
		2013-01-01
		
		Wrapper to obtain post value.
		
		$cID:		Index.
		$cDefault:	Default on not set.
		*/
		//echo $_POST[$cID];
		
		return $this->utl_validate_isset($_POST[$cID], $cDefault);
	}
	
	public function utl_get_server_value($cID, $cDefault=NULL)
	{
		/*
		utl_get_server_value
		Damon Vaughn Caskey
		2013-01-01
		
		Wrapper to obtain server value.
		
		$cID:		Index.
		$cDefault:	Default on not set.
		*/
		
		return $this->utl_validate_isset($_SERVER[$cID], $cDefault);
	}
	
	public function utl_str_to_array($cList=NULL, $cDel=",")
	{	
		/*
		str_to_array
		Damon Vaughn Caskey
		2013-01-09
		
		Break string into indexed array with no spaces.
		
		$cList:	List array to break up.
		$cDel:	Delimiter.
		*/
	
		/*
		If list is populated remove spaces and break into array.
		*/
		if($cList)									//List populated?								
		{
			$cList = str_replace (" ", "", $cList);	//Remove spaces.
			$cList = explode($cDel, $cList);		//Break into array.
		}
		
		/*
		Return end result.
		*/
		return $cList;
	}
	
	public function utl_redirect($cURL=NULL)
	{	
		/*
		utl_redirect
		Damon Vaughn Caskey
		2013-01-09
		
		Send header that redirects client to new page.
		
		$cURL:	Target address.
		*/

		/*
		If headers haven't been sent, redirect user to an error page. Otherwise we'll just have to die and settle for a plain text message.
		*/
		
		if(headers_sent())
		{ 
			/*
			Good coding will always avoid attempting to resend headers, but let's make sure to catch them here before PHP throws a nasty error.
			*/			
		}
		else
		{			
			header('Location: '.$cURL);
			return TRUE;
		}
		
		/*
		Return end result.
		*/
		return FALSE;
	}
	
	public function utl_validate_email(&$cValue, $cDefault=NULL)
	{
		/*
		utl_validate_email
		Damon Vaughn Caskey
		2013_01_01
		
		Validate email variable.
		
		$cValue:	Email value.
		$cDefault:	Default on fail.
		*/
				
		if(filter_var($cValue, FILTER_VALIDATE_EMAIL)) 
		{			
			list($user,$domaine)=explode("@", $cValue, 2);
			
			if(!checkdnsrr($domaine, "MX")&& !checkdnsrr($domaine, "A"))
			{
				/*
				Bad domain.
				*/
				$cValue = FALSE;
			}
			else 
			{
				//Domain OK.
			}
		}
		else 
		{		
			/*
			Bad address.
			*/	
			$cValue = FALSE;
		} 
		
		if($cValue == FALSE)
		{
			$cValue = $cDefault;
		}
		
		return $cValue;
	}
	
	public function utl_validate_ip(&$cValue, $cDefault=NULL)
	{
		/*
		utl_validate_ip
		Damon Vaughn Caskey
		2013-01-01
		
		Validate ip variable.
		
		$cValue:	ip value.
		$cDefault:	Default on fail.
		*/
		
		$cValue = filter_var($cValue, FILTER_VALIDATE_IP);		
		
		if($cValue == FALSE)
		{
			$cValue = $cDefault;
		}
		
		return $cValue;
	}
	
	public function utl_validate_isset(&$cValue, $cDefault=NULL)
	{
		/*
		utl_validate_isset
		Damon Vaughn Caskey
		2013-01-01
		
		Return default if variable is not set.
		
		$cValue:	Value.
		$cDefault:	Default on not set.
		*/
		
		if(!isset($cValue))
		{
			$cValue = $cDefault;
		}
		
		return $cValue;
	}
	
	public function utl_if_exists($cValue, $cValPrefix=NULL, $cValCadence=NULL, $cAlt=NULL)
	{
		/*
		utl_
		Damon Vaughn Caskey
		2013-01-01
		
		Return self if self has a value, or $cAlt if $cValue is empty or null. Reduces need to retype and 
		potentiality mix up variable names or array keys twice in common "echo <X> if it has a value" situations.

		Preconditions:
			$cValue: Value to test and return if it exists.
			$cValPrefix: Add to front of $cValue on return.
			$cValCadence: Add to end of $cValue on return.
			$cAlt: Value to return if $cValue is NULL.
		*/
		
		/* Vaiables */
		$cReturn = $cAlt;	//Final value to return.
		
		/* Value exists? */
		if($cValue)
		{
			/* Tack on additions and return value. */			
			$cReturn = $cValPrefix.$cValue.$cValCadence;
		}
		
		return $cReturn;		
	}
}
?>



Class – Training

<?php

class class_training
{    

	/*
	constants
	Damon Vaughn Caskey
	2012_12_18
	
	Global constants. 
	*/		
		
	private	$oDB		= NULL;	//Class object: Database.
	private $oDBA		= NULL;	//Class object: Database (answers).
	private $oSes		= NULL;	//Class object: Session.
	private	$oUtl		= NULL;	//Class object: Filter.
	private	$oErr		= NULL;	//Class object: Error.
	private $oFrm		= NULL;	//Class object: Forms. 		
	private $cQuery		= NULL;
	private $cParams	= NULL;
	private $cTVarsTD	= NULL;	//Class variable ID array.
	private $cTVars		= NULL;
	
	function __construct($oDep)
	{
		/*
		Constructor
		Damon Vaughn Caskey
		2012_12_29
		
		Class constructor.
		*/
		
		/* Import object dependencies. */
		$this->oSes	= $oDep['Ses'];
		$this->oDB 	= $oDep['DB'];
		$this->oFrm	= $oDep['Frm'];
		$this->oFil	= $oDep['Fil'];
		$this->oErr	= $oDep['Err'];
		$this->oDBA	= new class_db(array('Utl' => $oDep['Utl'], 'Err' => $oDep['Err']));
				
		/* Verify object dependencies. */
		if(!$this->oDBA)	trigger_error("Missing object dependency: Database (Ans).", E_USER_ERROR);
		if(!$this->oDB)		trigger_error("Missing object dependency: Database.", E_USER_ERROR);
		if(!$this->oSes)	trigger_error("Missing object dependency: Session.", E_USER_ERROR);	
		if(!$this->oFrm)	trigger_error("Missing object dependency: Forms.", E_USER_ERROR);	
		if(!$this->oFil)	trigger_error("Missing object dependency: Filter.", E_USER_ERROR);		
		if(!$this->oErr)	trigger_error("Missing object dependency: Error.", E_USER_ERROR);		
	}
	
	public function training_quiz_grade($cQuizID)
	{
		$cQuestionCount = NULL;
		$cQuestionRight = NULL;	
		$val			= NULL;	
		$iQueCnt		= 0;
		$cResponse		= NULL;
		$cQuizGradeStr	= NULL;
		$ans			= 0;
		$cPercentage	= 0;
								
		$cQuestionCount = $this->oSes->session_get('quiz_question_count');
		$cQuestionRight = $this->oSes->session_get('quiz_answer_right');
		
		$cQuestionRight = str_replace (" ", "", $cQuestionRight);	//Remove spaces.
		$cQuestionRight = explode(",", $cQuestionRight);			//Break into array.
		
		foreach($cQuestionRight as $val)				//Loop array collection.
		{		
			/* Increment counter. */
			$iQueCnt++;					
			
			/* Get question response */
			$cResponse = $this->oFil->utl_get_post("Q".$iQueCnt);
							
			$cQuizGradeStr.= "Question ".$iQueCnt." response: ";	
			
			if (!$cResponse)
			{
				$cQuizGradeStr.= "No answer.<br/>";
			}
			else				
			{
				if ($cResponse == $val)
				{
					$cQuizGradeStr.= $cResponse." - Correct.<br/>";
					$ans++;
				}
				else
				{
					$cQuizGradeStr.= $cResponse." - Incorrect.<br/>";					
				}	
			}							
		}
		
		$cPercentage	= round(($ans / $iQueCnt)*100);
			
		$cQuizGradeStr.= "<br />You answered ".$ans. " of ".$iQueCnt. " correctly (".$cPercentage."%).<br /><br />";
					
		$array["ans"] 			= $ans;
		$array["percentage"] 	= $cPercentage;
        $array["text"] 			= $cQuizGradeStr;
        			
		return $array;		
		
	}
		
	public function training_quiz_questions($cQuizID, $cOrder=NULL, $cQuantity=NULL)
	{
		/*
		training_quiz_questions
		Damon V. Caskey
		2011_11_29
		~2012_12_23: DB Class.
		
		Populate question list and possible answers from database from Quiz ID.
		
		$cQuizID:	Quiz ID to get quiz questions from. 
		*/		
  					
		$cQuizStr 			= NULL;								//Final output string to be placed into page.
		$QuestionID			= NULL;								//Current question ID in question loop.	
		$cQuestionCount		= 0;								//Include ID into question header.		
		$cQuestionRight		= NULL;								
				
		if(!$cOrder)
		{
			$cOrder = NULL;	//Order in table.	
		}
		else if($cOrder == 1)
		{
			$cOrder = "ORDER BY	question_order";	//User specified order.
		}
		else if($cOrder == 2)
		{
			$cOrder = "ORDER BY	newid()";	//Random order	
		}
		
		$cQuantity = $cQuantity ? "TOP ".$cQuantity : NULL;
		
		/* Construct questions query string. */
		$this->cQuery = "SELECT "
			.$cQuantity
			." *
			FROM 		tbl_class_train_questions
			WHERE		fk_tbl_class_train_parameters_guid_id = ? "
			.$cOrder;	
		
		/* Apply parameters. */
		$this->cParams = array(&$cQuizID);
		
		/* Execute questions query. */
		$this->oDB->db_basic_select($this->cQuery, $this->cParams);
		
		/* Construct answers query string. */
		$this->cQuery = "SELECT 
			* 
			FROM 	tbl_class_train_answers
			WHERE	fk_tbl_class_train_questions_guid_id = ?
			ORDER BY answer_order, value";
		
		/* Apply parameters. */
		$this->cParams = array(&$QuestionID);
		
		while($this->oDB->db_line())
		{
			$QuestionID = $this->oDB->cDBLine["guid_id"];
			
			/*	Get answer set matching current question ID. */
			$this->cParams = array(&$QuestionID);
			
			/*Record answer array. First element will be blank. ( ", A, C, B, A, etc.") */
			if($cQuestionRight)
			{
				$cQuestionRight .= ", " .$this->oDB->cDBLine["right_answer"];
			}
			else
			{
				$cQuestionRight = $this->oDB->cDBLine["right_answer"];
			}
			
			/* Build question string. */
			$cQuizStr 	.="<p><span class='TrainQuestionHeader'>"
						."Question ".++$cQuestionCount			
						."</span><br />"
						."<span class='TrainQuestionText'>"			
						.$this->oDB->cDBLine["question"]		
						."</span><br />";			
			
			/* Execute answers query. */
			$this->oDBA->db_basic_select($this->cQuery, $this->cParams);
			
			while ($this->oDBA->db_line())
    		{			
				$cQuizStr	.=	"<input type='radio' name='Q"					
							.$cQuestionCount							
							."' value='"	
							.$this->oDBA->cDBLine["value"]	
							."' />"
							."<span class='TrainQuestionAnswerHeader'>"									
							.$this->oDBA->cDBLine["value"]
							.")</span> "
							."<span class='TrainQuestionAnswerText'>"										
							.$this->oDBA->cDBLine["text"]
							."</span><br />";							
			}						
		}			
		
		if(!$cQuizStr)
		{
			$cQuizStr = "<h2><span class='alert'>No questions available.</span></h2>";
		}
		
		$this->oSes->session_set('quiz_answer_right', $cQuestionRight);
		$this->oSes->session_set('quiz_question_count', $cQuestionCount);
				
		return $cQuizStr;
	}
	
	public function training_quiz_questions_setup($cQuizID, $cOrder=NULL, $cQuantity=NULL)
	{		
		/*
		class_quiz_questions_0003
		Damon V. Caskey
		2012_10_04
		
		Create form to modifiy questions and answers.
		
		$cQuizID:	Quiz ID to get quiz questions from. 
		*/		
  			
		$cQuizStr 			= NULL;								//Final output string to be placed into page.
		$cQuery				= NULL;								//Query string.
		$cParams			= NULL;								//Parameter array.
		$oDBA				= NULL;								//Database class object (answers). 
		$QuestionID			= NULL;								//Current question ID in question loop.	
		$cOrderLst			= NULL;								//Droplist values for question order.
		$cAnswerValLst		= NULL;
		
		/* Initialize database class objects. */
		$oDBA	= new class_db(array("Utl" => $oUtl, "Err" => $oErr));					//Database class object (Answers).
		
		/* Prepare answer dropdown list values */
		$cAnswerValLst 		= array_merge(array("-" => "-"), range('A', 'Z'));
				
		/* Construct questions query. */
		$cQuery = "SELECT "
			.$cQuantity
			." *
			FROM 		tbl_class_train_questions
			WHERE		fk_tbl_class_train_parameters_guid_id = ? 
			ORDER BY	question_order";	
						
		/* Apply question parameters. */
		$cParams = array(&$cQuizID);
		
		/* Execute questions query */
		$oDB->db_basic_select($cQuery, $cParams);						
		
		$cQuery = "SELECT 
						* 
						FROM 	tbl_class_train_answers
						WHERE	fk_tbl_class_train_questions_guid_id = ?
						ORDER BY answer_order, value";
		
		/* Apply answer parameters. */
		$cParams = array(&$QuestionID);
		
		/* Prepare question order dropdown list values */
		$cOrderLst = range(1, $oDB->iDBRowCount);
			
		while($oDB->db_line())
		{
			$QuestionID = $oDB->cDBLine["guid_id"];						
			$cAnswers = NULL;					  
												
			$oDBA->db_basic_select($cQuery, $cParams);
			
			while($oDBA->db_line())
			{			
				$cAnswers .= "  
					
					<form name='frm_answer_update' id='frm_answer_update_".$oDBA->cDBLine["guid_id"]."' method='post' action='".$_SERVER['PHP_SELF']."#a_question_".$oDB->cDBLine["question_order"]."'>
						<input type='hidden' name='EditMode' value='1'  />
						<input type='hidden' name='id_guid' value='".$oDBA->cDBLine["guid_id"]."' />
						  <table width='100%' border='0' cellspacing='0' cellpadding='2' bgcolor='#DDDDFF'>
							<tr>
							  <td width='10%'>
								  <select name='frm_lst_answer_val' id='frm_lst_answer_val_".$oDBA->cDBLine["guid_id"]."'>"
									.$this->oFrm->forms_select_options($cAnswerValLst, NULL, $oDBA->cDBLine["value"], FALSE).
								  "</select>
							  </td>
							  <td width='70%'><textarea name='frm_ta_answer_text' id='frm_ta_answer_text_".$oDBA->cDBLine["guid_id"]."' cols='35' rows='1'>".$oDBA->cDBLine["text"]."</textarea></td>
							  <td width='10%' align='center'><input type='image' src='/media/image/icon_save_0001.png' name='frm_btn_ans_save' id='frm_btn_save_ans_".$oDBA->cDBLine["guid_id"]."' value='Save' /></td>
							  <td width='10%' align='center'><input type='image' src='/media/image/icon_delete_0001.png' name='frm_btn_ans_delete' id='frm_btn_delete_ans_".$oDBA->cDBLine["guid_id"]."' value='Delete' /></td>
							</tr>
						  </table>
					</form><br />";									
										
			}				  
		  
			$cAnswers .= "<div id='new_answer_".$oDB->cDBLine["guid_id"]."'>

		  	<form name='frm_answer_new' id='frm_answer_new_".$oDB->cDBLine["guid_id"]."' method='post' action='".$_SERVER['PHP_SELF']."#a_question_".$oDB->cDBLine["question_order"]."'>
					<input type='hidden' name='EditMode' value='1'  />
					<input type='hidden' name='id_guid' value='".$oDB->cDBLine["guid_id"]."' />
					  <table width='100%' border='0' cellspacing='0' cellpadding='2' bgcolor='#DDDDFF'>
						<tr>
						  <td width='10%'>                          
							  <select name='frm_lst_answer_val' id='frm_lst_answer_val_".$oDB->cDBLine["guid_id"]."' >"
							   .$this->oFrm->forms_select_options($cAnswerValLst, NULL, "-", FALSE). 
							  "</select>
						  </td>
						  <td width='70%'><textarea name='frm_ta_answer_text' id='frm_ta_answer_text_".$oDB->cDBLine["guid_id"]."' cols='35' rows='1'></textarea></td>
						  <td width='20%' align='center'><input type='image' src='/media/image/icon_save_0001.png' name='frm_btn_ans_add' id='frm_btn_ans_add_".$oDB->cDBLine["guid_id"]."' value='Add' /></td>
						</tr>
					  </table>
				</form></div>";
		  
			  $cQuizStr 	.= "
			  <br />
			  <div id='question_".$oDB->cDBLine["guid_id"]."'>
			  <table width='100%' border='0' cellspacing='0' cellpadding='0' bgcolor='#F0F0FF'>
				<tr>
				  <td>
				  
					  <a name='a_question_".$oDB->cDBLine["question_order"]."' id='a_question_".$oDB->cDBLine["question_order"]."'></a>
					  <form name='frm_question_update' id='frm_question_update_".$oDB->cDBLine["guid_id"]."'  method='post' action='".$_SERVER['PHP_SELF']."#a_question_".$oDB->cDBLine["question_order"]."'>							  
					  <input type='hidden' name='EditMode' value='1'  />
					  <input type='hidden' name='id_guid' value='".$oDB->cDBLine["guid_id"]."' />
						<table width='100%' border='0' cellspacing='0' cellpadding='1' >
						  <tr>
							<th colspan='2'><table width='100%' border='0' cellspacing='0' cellpadding='0'>
							  <tr>
								<td width='77%' align='left'>Question
								  <select name='frm_lst_order' id='frm_lst_order_".$oDB->cDBLine["guid_id"]."'>"
									.$this->oFrm->forms_select_options($cOrderLst, NULL, $oDB->cDBLine["question_order"], FALSE).
								  "</select>
								  
								  </td>
								<td width='11%'><input type='submit' name='frm_btn_que_save' id='frm_btn_que_save_".$oDB->cDBLine["guid_id"]."' value='Save' /></td>
								<td width='12%'><input type='submit' name='frm_btn_que_delete' id='frm_btn_que_delete_".$oDB->cDBLine["guid_id"]."' value='Delete' onclick='return confirmSubmit()' /></td>
							  </tr>
							</table></th>
						  </tr>
						  <tr>
							<td><label for='frm_ta_question_val'>Question Text</label></td>
							<td><textarea name='frm_ta_question_val' id='frm_ta_question_".$oDB->cDBLine["guid_id"]."' cols='45' rows='5'>".$oDB->cDBLine["question"]."</textarea></td>
						  </tr>
						  <tr>
							<td><label for='frm_lst_right_answer'>Correct Response</label></td>
							<td><select name='frm_lst_right_answer' id='frm_lst_right_answer_".$oDB->cDBLine["guid_id"]."' >"
							  .$this->oFrm->forms_select_options($cAnswerValLst, NULL, $oDB->cDBLine["right_answer"], FALSE).
							"</select></td>
						  </tr>
						</table>
					  </form><br />"
					  
					  ."<div id='answers_".$oDB->cDBLine["guid_id"]."'>"
					  .$cAnswers
																 
				  ."</div></td>
				</tr>
			  </table></div>";			
								
		}	
		
			$cOrderLst[$oDB->iDBRowCount+1] = $oDB->iDBRowCount+1; //Add one more to the order list.
		
			$cQuizStr .= "<br />
					  <div id='question_new'>
					  <table width='100%' border='0' cellspacing='0' cellpadding='0' bgcolor='#F0F0FF'>
						<tr>
						  <td>						  
							  <a name='a_question_".($oDB->iDBRowCount+1)."' id='a_question_".($oDB->iDBRowCount+1)."'></a>
							  <form name='frm_question_new' id='frm_question_new'  method='post' action='".$_SERVER['PHP_SELF']."#a_question_".($oDB->iDBRowCount+1)."'>
							  <input type='hidden' name='trigger_type' value='question_add'  />
							  <input type='hidden' name='id_guid' value='".$oDBA->cDBLine["guid_id"]."' />
							  <input type='hidden' name='EditMode' value='1'  />
							  	<table width='100%' border='0' cellspacing='0' cellpadding='1' >
								  <tr>
									<th colspan='2'><table width='100%' border='0' cellspacing='0' cellpadding='0'>
									  <tr>
										<td width='77%' align='left'>Question
										  <select name='frm_lst_order' id='frm_lst_order_".$oDB->cDBLine["guid_id"]."'>"
											.$this->oFrm->forms_select_options($cOrderLst, NULL, ($oDB->iDBRowCount+1), FALSE).
										  "</select>
										  
										  </td>
										<td width='11%'><input type='submit' name='frm_btn_que_add' id='frm_btn_que_add' value='Add' /></td>
									  </tr>
									</table></th>
								  </tr>
								  <tr>
									<td><label for='frm_ta_question_val'>Question Text</label></td>
									<td><textarea name='frm_ta_question_val' id='frm_ta_question_val' cols='45' rows='5'></textarea></td>
								  </tr>
								  <tr>
									<td><label for='frm_lst_right_answer'>Correct Response</label></td>
									<td><select name='frm_lst_right_answer' id='frm_lst_right_answer' >"
									  .$this->oFrm->forms_select_options($cAnswerValLst, NULL, NULL, FALSE).
									"</select></td>
								  </tr>
								</table>
							  </form>
							</td>
						</tr>
					  </table></div>";
		
		if(!$cQuizStr)
		{
			$cQuizStr = "<h2><span class='alert'>No questions available.</span></h2>";
		}
			
		return $cQuizStr;
	}
	
	public function training_class_record($cTrainingParams)
	{		
		/*
		training_class_record_0001
		Damon Vaughn Caskey
		2013-03-27 (Converted to function class_record_0001 include)
		
		Inserts user variables into class participant database.	
		*/
		
		$cQuery		= NULL;	//Query string.
		$cParams	= NULL;	//Parameter array.
		$cClassID	= NULL;	//Class ID.
		$p_id		= NULL;	//Participant ID.
		$listing_id	= NULL;	//Class listing ID.
		
		/* Build query string. */
		$cQuery ="MERGE INTO tbl_class_participant
		USING 
			(SELECT ? AS Search_Col) AS SRC
		ON 
			tbl_class_participant.account = SRC.Search_Col
		WHEN MATCHED THEN
			UPDATE SET
				name_l				= ?,
				name_f				= ?,									
				room				= ?,
				status				= ?,
				phone				= ?,									
				department			= ?,
				supervisor_name_f	= ?,
				supervisor_name_l	= ?
		WHEN NOT MATCHED THEN
			INSERT (account, name_l, name_f, room, status, phone, department, supervisor_name_f, supervisor_name_l)
			VALUES (SRC.Search_Col, ?, ?, ?, ?, ?, ?, ?, ?)
			OUTPUT INSERTED.id_int;";		
		
		/* Apply parameters. */
		$cParams = array(&$cTrainingParams['account'],
						&$cTrainingParams['name_l'],
						&$cTrainingParams['name_f'],
						&$cTrainingParams['room'],
						&$cTrainingParams['status'],
						&$cTrainingParams['phone'],
						&$cTrainingParams['department'],
						&$cTrainingParams['supervisor_name_f'],
						&$cTrainingParams['supervisor_name_l'],
						&$cTrainingParams['name_l'],
						&$cTrainingParams['name_f'],
						&$cTrainingParams['room'],
						&$cTrainingParams['status'],
						&$cTrainingParams['phone'],
						&$cTrainingParams['department'],
						&$cTrainingParams['supervisor_name_f'],
						&$cTrainingParams['supervisor_name_l']);	
		
		/* Execute query. */	
		$this->oDB->db_basic_action($cQuery, $cParams, TRUE);
		
		/* Get ID of created/updated record. */
		$p_id = $this->oDB->cDBLine["id_int"];
		
		/* 	User demographics have now been found or inserted. Now we will deal with class type, instructor and time. */		
		$cQuery = "INSERT INTO	tbl_class
		
								(class_type,
								trainer_id,
								class_date)
					OUTPUT INSERTED.class_id
								VALUES	(?, ?, ?)";	
		
		$cParams = array(&$cTrainingParams['class'],
			&$cTrainingParams['trainer'],
			&$cTrainingParams['taken']);
						
		/* Execute query. */	
		$this->oDB->db_basic_action($cQuery, $cParams, TRUE);
		
		/* Get ID of new record. */		
		$cClassID = $this->oDB->cDBLine["class_id"];
					
		/* Insert newly created id and participant id to class listing table. */		
		$cQuery = "INSERT INTO tbl_class_listing
		
								(participant_id,
								class_id)
					OUTPUT INSERTED.id_int
								VALUES (?, ?)";	
		
		$cParams = array(&$p_id,
						&$cClassID);
						
		/* Execute query. */	
		$this->oDB->db_basic_action($cQuery, $cParams, TRUE);
		
		/* Get ID of new record. */		
		return $this->oDB->cDBLine["id_int"];			  
	}
	
	public function training_vars_get()
	{
		//foreach ($this->cClassVars as $key => $val)
		//{
		//	$this 		
		//}
	}
}



Class – Tables

<?php 

class class_tables {

	/*
	Tables
	Damon Vaughn Caskey
	2013-03-21
	
	Miscellaneous table functions.
	*/
	
	public 	$cMarkup 		= NULL;	//Resulting markup output. Typically a table.
	private $oUtl			= NULL;	//Utility class object.
	
	function __construct($oDep)
	{
		/*
		Constructor
		Damon Vaughn Caskey
		2013_01_21
		
		Class constructor.
		*/
		
		/* Import object dependencies. */
		$this->oUtl = $oDep['Utl'];
						
		/* Verify object dependencies. */
		if(!$this->oUtl)	trigger_error("Missing object dependency: Utility.", E_USER_ERROR);		
	}
	
	public function tables_db_output($oDB, $bRowCount = TRUE, $cFieldSkip = NULL, $cAuxLink = array("Header" => NULL, "Link" => NULL, "Target" => "_blank", "Text" => "Go"), $cAuxLinkFields = NULL, $cRowStyle = NULL)
	{	
		/*
		tables_db_output
		Damon Vaughn Caskey
		2013-03-20
		
		Create complete table markup from database query.
		
		Preconditions:
			Executed database query.
			$oDB: Object with object variables populated by query.
			$bRowCount: True = Display a row count preceding table.
			$cFieldSkip['<fieldname>', ...]: Array of fields from query to skip when creating table.
			$cAuxLink['Link', 'Text', 'Target']: Adds action link to end of table.
				Link: Page name.
				Target: Page target (_new, _blank, etc.)
				Text: Text to display.				
			$cAuxLinkFields['<fieldname>', ...]: Fields to pass as part of action link.
			$cRowStyle[even, odd]: Array to override default alternate row style.
				
		Postconditions:
			Populate and return $cMarkup with table markup string.
		*/
		
		$i						= NULL;	//Working counter.
		$iRowCount 				= NULL;	//Count of records retrieved by query.
		$cOutput				= NULL;	//Output string.
		$cFieldMetadata			= NULL;	//Metadata collection (field name, type, etc.) for database columns.
		$cFieldMetaDataName 	= NULL; //Individual item name from metadata colelction.
		$cFieldMetaDataValue	= NULL;	//Individual item value from metadata collection.
		$iFields				= NULL;	//Field counter/index.
		$cFieldName				= NULL; //Field name array.
		$cName					= NULL;	//Table markup write in: Field name.
		$cValue 				= NULL;	//Table markup write in: Field value.
		$cLink 					= NULL;	//Table markup write in: Action link.
		
		/* Add extra markup if cAuxLink has a value, otherwise leave NULL. " */
		
		if($cAuxLink["Link"] != NULL)
		{			
			$cAuxLink["Link"] .= "?";
			$cAuxLink["Header"] = $cAuxLink["Header"] != NULL ? "<th>".$cAuxLink["Header"]."</th>" : "<th>Action</th>";			
		}
		
		if($bRowCount)
		{
			$iRowCount = $oDB->iDBRowCount; 
			$cOutput .= '<span class="row_count">' .$iRowCount. ' records found.</span><br/><br/>';
		}
		
		$cOutput .= '<div title="Table" class="overflow"><table border="0" cellpadding="5" cellspacing="0" bgcolor="#CCCCCC"><tr>';	
		
		/* Zero counter */
		$i = 0;
		
		/* Loop each column in query result. */	
		foreach($oDB->cDBMeta as $cFieldMetadata)
		{					
			/* Loop coloumn metadata collection. */
			foreach($cFieldMetadata as $cFieldMetaDataName => $cFieldMetaDataValue)
			{			
				/* Column name? */				
				if($cFieldMetaDataName == 'Name')
				{						
					/* Check field skip array before using this field name */
					if(!in_array($cFieldMetaDataValue, $cFieldSkip))
					{						
						/* Output to table header markup and populate name array. */									
						$cOutput .= "<th>".$cFieldMetaDataValue."</th>";	//Populate table header markup.																	
					}
					
					$cFieldName[$i] = $cFieldMetaDataValue;				//Populate Name array.
					
					/* Increment field count. */
					$iFields++;
				}					
			}
			
			/* Increment counter. */
			$i++;							
		}		
					
		$cOutput .= $cAuxLink["Header"];	
				
		/* Output query results as table. */
		while($oDB->db_line(SQLSRV_FETCH_NUMERIC))
		{		
		
			$cLink 		= $cAuxLink["Link"];
			
			/* Increment line counter */
			$iLine++;
			
			/* Insert table row and style. */
			$cOutput .= "<tr bgcolor='".$this->oUtl->utl_color_alternation($iLine)."'>";
											
			for ($i = 0; $i < $iFields; $i++)
			{					
				$cName		= $cFieldName[$i];
				$cValue 	= $oDB->cDBLine[$i];								
			
				/* Check field skip array before using this field. */
				if(!in_array($cName, $cFieldSkip))
				{				
					$cOutput .= "<td>".$cValue."</td>";
				}
				
				if($cLink != NULL)
				{			
					if(in_array($cName, $cAuxLinkFields))
					{
						$cLink .= $cFieldName[$i]."=".$cValue."&";
					}				
				}
			}
			
			if($cLink != NULL)
			{
				$cOutput .= '<td><a href="'.$cLink.'" target="'.$cAuxLink["Target"].'">'.$cAuxLink["Text"].'</a></td>';								
			}
		}
					
		$cOutput .= "</table><br/><br/></div>";
		
		$this->cMarkup = $cOutput;
		
		return $this->cMarkup;
	}		
}
?>



Class – Sessions

<?php

class class_sessions implements SessionHandlerInterface
{    

	/*
	class_sessions
	Damon Vaughn Caskey
	2012_12_10
	
	Override PHP's default session handling to store data in an MSSQL table. 
	*/	
	
	const 	c_iLife 	= 1440;	//Default session time out (in seconds)
	
	private	$oDB 		= NULL;	//Databse class object.
	private $iLife		= NULL;	//Session time out.

	function __construct($oDep, $iLife=self::c_iLife)
	{
		/*
		Constructor
		Damon Vaughn Caskey
		2012_12_29
		
		Class constructor.
		*/		
								
		/* Set class vars. */
		$this->iLife = $iLife;	//Session time out.
		
		/* Import object dependencies. */
		$this->oDB = $oDep['DB'];
				
		/* Verify object dependencies. */
		if(!$this->oDB)	trigger_error("Missing object dependency: Database.", E_USER_ERROR);		
	}
      
   	public function session_set($cID, $cValue=NULL)
	{
		/*
		session_set
		Damon Vaughn Caskey
		2012_12_23
		
		Wrapper to set value of a $_SESSION[] variable.
		
		$cID:		Session variable name/id.
		$cValue:	Value to set.
		*/
		
		$_SESSION[$cID] = $cValue;
	}
	
	public function session_get($cID)
	{
		/*
		session_get
		Damon Vaughn Caskey
		2012_12_23
		
		Wrapper to aquire value in a $_SESSION[] variable.
		
		$cID:	Session variable name/id.
		*/
		
		$cValue	= NULL;	//Value to return.
		
		/* Get session value if any */
		if(isset($_SESSION[$cID]))
		{
			$cValue = $_SESSION[$cID];
		}	
		
		/* Return value. */
		return $cValue;
	}	
   
   	public function open($savePath, $sessionName)
    {	
		/*
		open
		Damon Vaughn Caskey
		2012_12_10
		
		Set database class object for other session functions. Called by PHP to open session.
		
		$savePath: 		Path to locate session file. Unused.
		$sessionName:	Name of session file. Unused.
		*/
					
		/* Return TRUE. */
        return true;
    }

    public function close()
    {	
		/*
		close
		Damon Vaughn Caskey
		2012_12_10
		
		Filler; function is called by PHP to close session.
		*/			
		
		/* Return TRUE. */
        return true;
    }

    public function read($cID)
    {		
        /*
		read
		Damon Vaughn Caskey
		2012_12_10
		
		Locate and read session data from database.
		
		$cID = Session ID.
		*/
	
		$cData 		= NULL; 						//Final output.
		$cQuery 	= NULL;							//Query string.
		$cTime 		= date(constants::c_cDateF);	//Current time.
		$cParams	= NULL;							//Parameter array.						
					 
		/* Build query string. */
		$cQuery = "SELECT session_data 
					FROM tbl_php_sessions 
					WHERE
							session_id = ? 
						AND 
							expire > ?";
		
		/* Apply parameters. */
		$cParams = array(&$cID, &$cTime); 
		
		/* Execute query. */	
		$this->oDB->db_basic_select($cQuery, $cParams);
						
		/* Get result and pass to local var(s). */
		if($this->oDB->rDBResult)
		{
			/* Set line array. */
			$this->oDB->db_line();
			
			/* Get session data. */
			$cData = $this->oDB->cDBLine['session_data'];
		}	
		
		/* Return results. */
		return $cData;
    }

    public function write($cID, $cData)
    {
		/*
		write
		Damon Vaughn Caskey
		2012_12_10
		
		Update or insert session data. Note that only ID, Expire, and Session Data are 
		required. Other data is to aid in debugging.
		
		$cID 	= Session ID.
		$cData	= Session data.
		*/
		
		$cQuery = NULL;	               		//Query string.
		$cTime 	= NULL;						//Current time.
		$cLoc	= $_SERVER["PHP_SELF"];		//Current file.
		$cIP	= $_SERVER['REMOTE_ADDR'];	//Client IP address.
					
		/* Calculate epirire time. */
		$cTime		= date(constants::c_cDateF, time()+$this->iLife);	
		
		/* Ensure IP string is <= 15. Anything over is a MAC or unexpected (and useless) value. */
		$cIP = substr($cIP, 0, 15);
		
		/* Build query string. */
		$cQuery ="MERGE INTO tbl_php_sessions
		USING 
			(SELECT ? AS Search_Col) AS SRC
		ON 
			tbl_php_sessions.session_id = SRC.Search_Col
		WHEN MATCHED THEN
			UPDATE SET
				session_data	= ?,
				expire			= ?,
				source			= ?,
				ip				= ?
		WHEN NOT MATCHED THEN
			INSERT (session_id, session_data, expire, source, ip)
			VALUES (SRC.Search_Col, ?, ?, ?, ?);";		
		
		/* Apply parameters. */
		$cParams = array(&$cID,
				&$cData,
				&$cTime,
				&$cLoc,
				&$cIP,
				&$cData,				
				&$cTime,
				&$cLoc,
				&$cIP);	
		
		/* Execute query. */	
		$this->oDB->db_basic_action($cQuery, $cParams);
		
		/* Return TRUE. */
		return true;
    }

    public function destroy($cID)
    {	
	
		/*
		destroy
		Damon Vaughn Caskey
		2012_12_10
		
		Delete current session.
		
		$cID: Session ID.		 
		*/
				
		$cQuery 	= NULL;	//Query string.
		$cParams	= NULL;	//Parameter array.
		
		/* Build query string. */
		$cQuery		= "DELETE FROM tbl_php_sessions WHERE session_id = ?";
		
		/* Apply parameters. */
		$cParams	= array(&$cID);
		
		/* Execute query. */	
		$this->oDB->db_basic_action($cQuery, $cParams);		
		
		/* Return TRUE. */
		return true;
    }

    public function gc($maxlifetime)
    {
		/*
		gc (Garbage Cleanup)
		Damon Vaughn Caskey
		2012_12_10
		
		Delete expired session data.
		
		$maxlifetime: Expire time. Unused. 
		*/
		
		$cTime		= date(constants::c_cDateF);	//Current time.	
		$cQuery		= NULL;							//Query string.
		$cParams 	= NULL;							//Parameter array.
		
		/* Build query string. */
		$cQuery		= "DELETE FROM tbl_php_sessions WHERE expire < ?";
		
		/* Apply parameters. */
		$cParams	= array(&$cTime);

		/* Execute query. */	
		$this->oDB->db_basic_action($cQuery, $cParams);
	
		/* Return TRUE. */
		return true;
    }
}



Class – Error

<?php

class class_error
{    

	/*
	class_error
	Damon Vaughn Caskey
	2012_12_28
	
	Error handler.
	*/	
	
	const 	c_cDBEHost		= "box406.bluehost.com";	//Error log DB host.
	const 	c_cDBELName		= "caskeysc_uk";			//Error log DB logical name.
	const 	c_cDBEUser		= "caskeysc_ehsinfo";		//Error log DB user.
	const 	c_cDBEPword		= "caskeysc_ehsinfo_user";	//Error log DB password.
	const	c_iETScript		= 0;						//Error type; general script errors.
	const	c_iETDB			= 1;						//Error type; datbase error.
	
	private $cIP			= NULL;						//$_SERVER['REMOTE_ADDR']
	private $cSource		= NULL; 					//$_SERVER['PHP_SELF']
	private $debug			= 0;
	private $oMail			= NULL;						//Class mail hanlder.
	private $oUtl			= NULL;						//Utility functions.
	
	public	$cErrType		= NULL;						//Error number or user type.
	public 	$cErrCode		= NULL;						//Error code.
	public 	$cErrDetail		= NULL;						//Error detail (SQL string, parameters, user defined data...).
	public 	$cErrFile		= NULL;						//File running at error time.
	public	$cErrLine		= NULL; 					//Error line.
	public 	$cErrMsg		= NULL;						//Error message.
	public	$cErrState		= NULL;						//State of server (ex. SQL State).
	public	$cErrTOE		= NULL;						//Time of error.
	public	$cErrVars		= NULL;						//String dump of variables.
	
	public function __construct($oDep, $debug = 0)
    {
        /* Import object dependencies. */
		$this->oMail = $oDep['Mail'];
		$this->oUtl = $oDep['Utl'];
				
		/* Verify object dependencies. */
		if(!$this->oMail)	trigger_error("Missing object dependency: Mail.", E_USER_ERROR);
		if(!$this->oUtl)	trigger_error("Missing object dependency: Utility.", E_USER_ERROR);
		
		$this->debug = $debug;
        set_error_handler(array($this, 'error_handle_start'));
	
		register_shutdown_function(array(&$this, 'error_shutdown'));
	}
	
	public function error_fatal()
	{	
		/*
		error_fatal
		Damon Vaughn Caskey
		2012_12_30
		
		Run final actions before exit on a fatal error.
		*/
		
		/*
		If headers haven't been sent, redirect user to an error page. Otherwise we'll just have to die and settle for a plain text message.
		*/
		if($this->oUtl->utl_redirect("/a_errors/php.php")===FALSE)
		{ 
			die("I'm sorry; it appears an internal error has occurred while processing your request. The webmaster has been alerted and will resolve this issue as soon as possible.");
		}
		
		exit;
			
	}
	
	public function error_handle_start($cCode=NULL, $cMsg=NULL, $cFile=NULL, $cLine=NULL)
	{
		$this->error_handle($cCode, $cMsg, $cFile, $cLine, self::c_iETScript, NULL, NULL);
		
		return true;		
	}
	
	public function error_handle($cCode=NULL, $cMsg=NULL, $cFile=NULL, $cLine=NULL, $cType=self::c_iETScript, $cState=NULL, $cDetail=NULL)
	{
		/*
		error_run
		Damon Vaughn Caskey
		2012_12_28
		
		Run error mail and and log in single call.
				
		$cCode:		Error code/number.
		$cMsg:		Error message.
		$cFile:		PHP generated file name.
		$cLine:		Code line location.
		$cType:		User defined error type.
		$cState:	Server state (mostly for SQL errors).
		$cDetail:	User added detail.
		*/
		
		$iLevel = NULL;
		$value	= NULL;
		$key	= NULL;
		$i		= 0;
				
		$this->cErrTOE		= date(constants::c_cDateF);
		$this->cIP			= $_SERVER['REMOTE_ADDR'];
		$this->cSource		= $_SERVER['PHP_SELF'];
		$this->cErrType		= $cType;
		$this->cErrFile		= $cFile;
		$this->cErrLine		= $cLine;	
		$this->cErrState	= $cState;
		$this->cErrCode		= $cCode;
		$this->cErrMsg		= $cMsg;
		$this->cErrDetail	= $cDetail;
		$this->cErrVars		= NULL;
						
		/*
		If logging in (/authenticate_0001.php) and error is suppressed then exit and do nothing.
		
		LDAP libraries are bugged. EX: ldap_bind throws error 49 on bad password instead of returning FALSE as documented. 
		In PHP this can only be worked around by suppressing the error. Otherwise suppressing errors with @ is bad practice 
		that should be avoided at all costs. Its use will be ignored within any other file.
		*/
		$iLevel = error_reporting();
		
		if (($iLevel == 0 || ($iLevel & $cCode) == 0) && $this->cSource == "/authenticate_0001.php")
		{
			return true;
		}
				
		if($this->cErrCode)
		{
			/*
			Log error to database.
			*/
			//$this->error_log_db();
			
			/*
			If error is any type other than a notice then immediately end script and send an email alert to webmaster.
			*/
			switch ($this->cErrCode)
			{
				case E_USER_ERROR:
				case E_USER_WARNING:
				case E_ERROR:
				case E_CORE_ERROR:
				case E_COMPILE_ERROR:
				default:
					
					if(isset($_GET))
					{
						foreach($_GET as $key => $value)
						{
							$this->cErrVars .= "GET[".$key."]: ".$value." || ";
						}
					}
					
					if(isset($_POST))
					{
						foreach($_POST as $key => $value)
						{
							$this->cErrVars .= "POST[".$key."]: ".$value." || ";
						}
					}
					
					if(isset($_SESSION))
					{
						foreach($_SESSION as $key => $value)
						{
							$this->cErrVars .= "SESSION[".$key."]: ".$value." || ";
						}
					}
		
					$this->error_mail();
					$this->error_fatal();
					break;
				case E_USER_NOTICE:
				case E_NOTICE:
					break;				
			}		
		}
	}
		
   	public function error_log_db()
	{
		/*
		error_db_log
		Damon Vaughn Caskey
		2012_12_28
				
		Attempt to log error detail to database. Self contained to avoid recursive calls to database class.
		*/
				
		$rDBConn		= NULL;	//Connection reference to DB error log.
		$cQuery			= NULL; //Error query string.
		$rDBStatement	= NULL;	//Prepared query reference.
		
		$rDBConn = new mysqli(self::c_cDBEHost, self::c_cDBEUser, self::c_cDBEPword, self::c_cDBELName);
		
		/* If the error log database connection was successful, insert each error to table. */
		if (!$rDBConn->connect_error) 
		{				
			/* Build query string. */ 		
			$cQuery = "INSERT INTO tbl_gen_errors (toe, ip, type, source, file, line, state, code, vars, msg, details) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
			$rDBStatement = $rDBConn->prepare($cQuery);
			
			/* Bind parameters. */
			$rDBStatement->bind_param("sssssssssss", $this->cErrTOE, $this->cIP, $this->cErrType, $this->cSource, $this->cErrFile, $this->cErrLine, $this->cErrState, $this->cErrCode, $this->cErrVars, $this->cErrMsg, $this->cErrDetail);
			
			/* Execute and close query. */ 
			if($rDBStatement != false)
			{							
				$rDBStatement->execute();
				$rDBStatement->close();
			}
			
			/* Close DB connection. */
			$rDBConn->close();			
		}						
		
	}
	
	public function error_mail()
	{
		/*
		error_mail
		Damon Vaughn Caskey
		2012_12_31
		~2012_01_02: Array list upgrade.
		
		Prepare and send an email error alert.
		*/
		
		$cMsg 	= NULL;
		
		$cMsg = array(
			"Time"				=>	$this->cErrTOE,
			"Type"				=>	$this->cErrType,
			"IP"				=>	$this->cIP,
			"Def. Source File"	=>	$this->cSource,
			"Source File"		=>	$this->cErrFile,
			"Line"				=>	$this->cErrLine,
			"State"				=>	$this->cErrState,
			"Code"				=>	$this->cErrCode,
			"Message"			=>	$this->cErrMsg,
			"Variables"			=>	$this->cErrVars,
			"Details"			=>	$this->cErrDetail
		);
				
		$this->oMail->mail_send($cMsg, "Error Report");
	}
			
	public function error_shutdown()
	{
		/*
		error_shutdown
		Damon Vaughn Caskey
		2012_12_31
		
		Shutdown function to capture error types PHP will not normally allow custom error handlers to deal with.
		*/
		
		$cError	= NULL;		//Last error status.
		
		/*
		Get last error status.
		*/
        $cError = error_get_last();
		
		$this->error_handle($cError['type'], $cError['message'], $cError['file'], $cError['line']);
    }	
}


Class – Mail

<?php

class class_mail
{    

	/*
	class_mail - https://www.caskeys.com/dc/?p=5031
	Damon Vaughn Caskey
	2012_12_10
	
	Mail handler. 
	*/	
		
	const	c_bWMAlert	= TRUE;										//Send webmaster a blind copy?
	const	c_cEDefMsg	= "...";									//Default message.
	const	c_cEHead	= "MIME-Version: 1.0 \r\nContent-type: text/html; charset=iso-8859-1\r\n";	//Default email headers.
	const	c_cESubject	= "From EHS Web";							//Default outgoing email subject.
	const	c_cEWMIn	= "dvcask2@uky.edu";						//Default webmaster's incoming email address.
	const	c_cEWMOut	= "ehs_noreply@uky.edu";					//Default address when server sends mail.
				
	public function mail_send($cMsg=self::c_cEDefMsg, $cSubject=self::c_cESubject, $cTo=self::c_cEWMIn, $cFrom=self::c_cEWMOut, $cBcc=NULL, $bWMAlert=self::c_bWMAlert, $cHeader=self::c_cEHead, $cParams=NULL)
	{	
		/*
		mail_send
		Damon Vaughn Caskey
		2012_12_28
		
		Send HTML mail with standard defaults.
		
		$cMsg:		Body of email.
		$cSubject:	Subject line.
		$cTo:		Outgoing address list.
		$cFrom:		Return address.
		$cBcc:		Blind carbon copy address list.
		$bWMAlert:	Send Bcc to webmaster.
		$cHeader:	Header information.
		$cParams:	Optional parameters.
		*/
	
		$cBody = NULL;	//Final sting for message body.
	
		/*
		Insert From address to header.
		*/
		$cHeader .= "From: ".$cFrom. "\r\n";		
		
		/* 
		If Webmaster alert is on, insert address into Bcc and add to header. Otherwise just add Bcc to header as is.
		*/
		if($bWMAlert===TRUE)
		{			
			$cHeader .= "Bcc: ".self::c_cEWMIn. ", ".$cBcc."\r\n";	
		}
		else
		{
			$cHeader .= "Bcc: ".$cBcc."\r\n";
		}
		
		$cHeader .="\r\n";
		
		/*
		If message passed as a key array, break into list and output as table layout.
		*/		
		if (is_array($cMsg))
		{
			/*
			Initial html and table markup.
			*/
			$cBody = "<html>
						<head>
						  <title>".$cSubject."</title>
						</head>
						<body>
						  <h1>".$cSubject."</h1>
						  <table cellpadding='3'>";
			
			/*
			Get each item in array and place into two column table row.
			*/
			foreach($cMsg as $key => $value)
			{			
				$cBody .= "<tr><th>".$key.":</th><td>".$value."</td></tr>";			
			}	
			
			/*
			Add closing markup.
			*/
			$cBody .= "</table>
					</body>
					</html>";	
		}
		else
		{
			/*
			Output message as is.
			*/
			$cBody = $cMsg;
		}
			
		/*
		Run mail function.
		*/
		return mail($cTo, $cSubject, $cBody, $cHeader, $cParams);		
	}	
}

?>

Class – Forms

<?php 

class class_forms {

	/*
	Utility
	Damon Vaughn Caskey
	2013_01_21
	
	Miscellaneous form input functions.
	*/
		
	/* Constants */
	const FORMS_ID_USE_NAME						= NULL;		//Use name to generate ID.
	const FORMS_ITEM_ADDITIONS_NONE				= NULL;		//No manual additions to generated item list.
	const FORMS_LABEL_NONE						= NULL;		//No label for fieldset item.
	const FORMS_LABEL_BLANK						= 1;		//Blank label for fieldset item.
	const FORMS_LABEL_USE_ITEM_KEY				= 2;		//Use the item key of a field for its fieldset label or visible selection.
	const FORMS_LABEL_USE_ITEM_NAME				= 3;		//Use the item name of a field for its fieldset label or visible selection.
	const FORMS_LABEL_USE_ITEM_VALUE			= 4;		//Use the items value of a field for its fieldset label or visible selection.
	const FORMS_LABEL_TYPE_FIELDSET				= 0;		//Fieldset label.
	const FORMS_LABEL_TYPE_INLINE				= 1;		//Label text only; no formatting.
	const FORMS_LEGEND_NONE 					= NULL;		//No legend for fieldset.
	const FORMS_QUERY_PARAMETERS_NONE			= NULL;		//No query parameters.
	const FORMS_TYPE_RADIO						= 0;		//Radio type list.
	const FORMS_TYPE_SELECT						= 1;		//Select type list.
	const FORMS_VALUE_CURRENT_NONE				= NULL;		//Current (last selected) value.
	const FORMS_VALUE_DEFAULT_NONE				= NULL;		//Default selected value.
	const FORMS_VALUE_DEFAULT_NOW				= -1;		//Default to current time for specific field.
	const FORMS_EVENTS_NONE						= NULL;		//No events attached to fieldset item.
	const FORMS_READ_ONLY_OFF					= FALSE;
	const FORMS_READ_ONLY_ON					= TRUE;
		
	public $cItemsList 							= NULL;		//Array of items list for select/radio/etc.
	public $cFormElement						= NULL;		//Arracy to store completed element markup that will be placed in a fieldset.
	public $cFormElementActions					= NULL;		//Array of actions tied to form element (onchange, onclick, etc.).
	public $cFieldset							= NULL;		//Array of completed fieldset markups ready to echo into page.
	public $cFieldsetAddsA						= NULL;		//Array of additional instructions, links, etc. that may be added to fieldset above items.
				
	function __construct($oDep)
	{
		/*
		Constructor
		Damon Vaughn Caskey
		2013-01-21
		
		Class constructor.
		*/
		
		/* Import object dependencies. */
		//$this->oUtl = $oDep['Utl'];
		$this->oDB	= $oDep['DB'];
		//$this->Err	= $oDep['Err'];
				
		/* Verify object dependencies. */
		//if(!$this->oUtl)	trigger_error("Missing object dependency: Utility.", E_USER_ERROR);		
		if(!$this->oDB)		trigger_error("Missing object dependency: Database.", E_USER_ERROR);
		//if(!$this->Err)		trigger_error("Missing object dependency: Errors.", E_USER_ERROR);
	}	
	
	private function forms_events_markup($cEvents=self::FORMS_EVENTS_NONE)
	{
		/*
		forms_events_markup
		Damon Vaughn Caskey
		2013-03-26
		
		Prepare HTML markup string for fieldset form item events (onchange, onclick, etc.).
		*/
		
		if(isset($cEvents))
		{		
			foreach ($cEvents as $cKey => $cValue)
			{
				
				$cEvent .= $cKey.'="'.$cValue.'"';
			}
		}
		return $cEvent;
	}
	
	public function test()
	{
		echo self::FORMS_LABEL_USE_ITEM_VALUE;
	}
	
	private function forms_label_markup($iStyle=self::FORMS_LABEL_NONE, $iType=self::FORMS_LABEL_TYPE_FIELDSET, $cName=NULL, $cID=NULL, $cKey=NULL, $cValue=NULL)
	{
		/*
		forms_label_markup
		Damon Vaughn Caskey
		2013-03-26
		
		Prepare HTML markup string for fieldset form item labels.
		
		Preconditions:
			$cName: 	Name of item.
			$cID:		ID of item.
			$cKey:		Array key of item.
			$cValue: 	Array value of item.
			$iStyle: 	How to arrange label markup.
				self::FORMS_LABEL_USE_ITEM_KEY = The item's Key will be used as a label.
				self::FORMS_LABEL_USE_ITEM_VALUE = The item's Value will be used as label.
				self::FORMS_LABEL_BLANK = The label will be left blank.
				self::FORMS_LABEL_NONE = No label markuop at all.
		
		Postconditions:
			Return finished label markup string.		
		*/
		
		$cClosing = NULL;
		$cLabel = NULL;
		
		if($iType == self::FORMS_LABEL_TYPE_FIELDSET)
		{
			/* Prepare opening label markup. */
			$cLabel = '<div class="'.$cClass['Container_Label'].'"><label for="'.$cID.'">';
			$cClosing = '</label></div>';	
		}							
			
		switch($iStyle)
		{
			case self::FORMS_LABEL_USE_ITEM_KEY:				
				
				$cLabel .= $cKey.$cClosing;									
				break;
		
			case self::FORMS_LABEL_USE_ITEM_NAME:			
				
				$cLabel .= $cName.$cClosing;									
				break;
			
			case self::FORMS_LABEL_USE_ITEM_VALUE:
				
				$cLabel .= $cValue.$cClosing;											
				break;
			
			case self::FORMS_LABEL_BLANK:
			
				$cLabel .= $cClosing;
				break;				
			
			case self::FORMS_LABEL_NONE:
			
				$cLabel = NULL;
				break;
				
			default:
				
				$cLabel .= $iStyle.$cClosing;							
		}
		
		/* Return label markup string. */
		return $cLabel;
	}
	
	public function forms_list_array_from_query($cQuery=NULL, $cParams=array(self::FORMS_QUERY_PARAMETERS_NONE), $cAddsT=self::FORMS_ITEM_ADDITIONS_NONE, $cAddsB=self::FORMS_ITEM_ADDITIONS_NONE){		
		
		/*
		forms_list_array_from_query
		Damon Vaughn Caskey
		2012-12-19
		
		Create list array directly from query results.
		
		$iType:		Select, options, etc.		
		$cQuery:	SQL query string.
		$cParams:	Parameter array.
		$cDefault:	Default selection.
		$cCurrent:	Current selection.
		$bKey:		Use key as item label?
		$cAddsT:	Additional items to place at top of generated list.
		$cAddsB:	Additional items to place at bottom of generated list.
		*/
	
		$i		=	NULL;		//Counter.
		$cList	=	NULL;		//Output string.
		$cKey	=	NULL;
		
		$cList	=	array();
		
		/* Query for list items. */
		$this->oDB->db_basic_select($cQuery, $cParams);
				
		/* Populate list variable. */
		while($this->oDB->db_line(SQLSRV_FETCH_NUMERIC))
		{	
			/* Get key (used for items visible to user). */
			$cKey = $this->oDB->cDBLine[1];
			
			/* If fields exist beyond first key, append to a single key value. */
			if($this->oDB->iDBFCount > 2)
			{	
				/* Start with field #2 (third field) and append to key string until last field is reached. */
				for($i = 2; $i <= ($this->oDB->iDBFCount-1); $i++)
				{
					$cKey .= "; ". $this->oDB->cDBLine[$i];	
				}
			}
			
			/* Populate list array using visible portion of list values as key. */			 
			$cList[$cKey] = $this->oDB->cDBLine[0];									
		}
		
		/* Merge Additions. (As of 5.4 array_merge() will always reorder indexes). */
		if($cAddsT && is_array($cAddsT))
		{	
			$cList = $cAddsT + $cList;	
		}
		
		if($cAddsB && is_array($cAddsB))
		{	
			$cList += $cAddsB;	
		}	
		
		$this->cItemsList = $cList;
		
		/* Output final list value. */
		return $this->cItemsList;
	}
	
	function forms_list_numeric($iType=NULL, $cQuery=NULL, $cParams=NULL, $cDefault=NULL, $cCurrent=NULL, $bKey=self::FORMS_LABEL_USE_ITEM_KEY, $iStart=0, $cAddsT=NULL, $cAddsB=NULL)
	{		
		/*
		forms_list_numeric
		Damon V. Caskey
		2012-12-19		
		
		Generate a simple numeric list with maximum value based number of rows returned by query.
		
		$iType: 	
		$cQuery:	SQL query string.
		$cParams:	Parameter array.
		$cDefault:	Default selection.
		$cCurrent:	Current selection.
		$bKey:		Use key as item label?
		$iStart:	First numeric value of generated list.
		$cAddsT:	Additional items to place at top of generated list.
		$cAddsB:	Additional items to place at bottom of generated list.
		*/
		
		$i		= NULL;	//Counter.
		$cList	= NULL;	//Item list array.					
		
		$cList	= array();
				
		$this->oDB->db_basic_select($cQuery, $cParams);	
								
		for($i = $iStart; $i <= $this->oDB->iDBRowCount; $i++)
		{		
			$cList[$i] = $i;
		}				
		
		/* Merge Additions. (As of 5.4 array_merge() will always reorder indexes). */
		if($cAddsT && is_array($cAddsT))
		{	
			$cList = $cAddsT + $cList;	
		}
		
		if($cAddsB && is_array($cAddsB))
		{	
			$cList += $cAddsB;	
		}
		
		switch ($iType)
		{
			case self::FORMS_TYPE_RADIO:
				//$this->cDLListCode = $this->forms_select_options($cList, $cDefault, $cCurrent, $bKey);
			default:
				$this->cDLListCode = $this->forms_select_options($cList, $cDefault, $cCurrent, $bKey);
		}
		
		return $this->cDLListCode;
	}
		                 
	public function forms_phone($cName=NULL, $cID=self::FORMS_ID_USE_NAME, $cLabel=self::FORMS_LABEL_NONE, $cDefault=array("cc" => "1", "ac" => "859", "lc" => NULL, "pn" => NULL), $cCurrent=self::FORMS_VALUE_CURRENT_NONE, $cClass=NULL, $cEvents=NULL)
	{	
		/*
		forms_phone
		Damon Vaughn Caskey
		2013-01-21
		
		Output form phone input markup.
		*/
	
		$cMarkup 	=	NULL;	//Final markup to echo.
		$cEvent		=	NULL;	//Event string.	
		
		$cMarkup .='<div class="'.$cClass['Container_All'].'">';
				
		/* Set ID to name? */
		$cID = $cID != self::FORMS_ID_USE_NAME ? $cID : $cName;
		
		/* If current value empty or NULL, set "No current" cosntant */
		$cCurrent = $cCurrent ? $cCurrent : self::FORMS_VALUE_CURRENT_NONE;
		
		$cDefault = $cDefault == self::FORMS_VALUE_DEFAULT_NONE ? NULL : $cDefault; 
		
		/* If no current value is available, use default. */
		if(!$cCurrent || $cCurrent == self::FORMS_VALUE_CURRENT_NONE)
		{
			$cCurrent = $cDefault;
		}
				
		/* Parse event actions. */
		$cEvent = $this->forms_events_markup($cEvents);			
		
		/* Prepare label markup. */
		$cMarkup .= $this->forms_label_markup($cLabel, self::FORMS_LABEL_TYPE_FIELDSET, $cName, $cID, $cKey, $cValue);		
		
		$cMarkup .= '<div class="'.$cClass['Container_Item'].'"><input type="text" name="'.$cName.'_cc" id="'.$cID.'_cc" class="'.$cClass.'" value="'.$cDefault['cc'].'" size="1" maxlength="1" />
						  (
							<input type="text" name="'.$cName.'_ac" id="'.$cID.'_ac" class="'.$cClass.'" value="'.$cDefault['ac'].'" size="3" maxlength="3" />
							) 
							<input type="text" name="'.$cName.'_lc" id="'.$cID.'_lc" class="'.$cClass.'" value="'.$cDefault['lc'].'" size="3" maxlength="3" /> 
						  - 
						  <input type="text" name="'.$cName.'_pn" id="'.$cID.'_pn" class="'.$cClass.'" value="'.$cDefault['pn'].'" size="4" maxlength="4" />				  
					</div></div>';		
		/*
		Return end result.
		*/
		return $cMarkup;
	}	
					
	public function forms_radio($cName=NULL, $cID=self::FORMS_ID_USE_NAME, $iLabelStyle=self::FORMS_LABEL_USE_ITEM_KEY, $cList=array(NULL), $cDefault=NULL, $cCurrent=self::FORMS_VALUE_CURRENT_NONE, $cClass=array("Item" => "Standard"), $cEvents=NULL)
	{
		/*
		forms_radio
		Damon Vaughn Caskey
		2013-03-24	
		*/
								
		$cKey		=	NULL;	//Array key.
		$cValue		=	NULL;	//Array value.
		$cChecked	= 	NULL;	//Checked (default/current)?
		$cMarkup 	=	NULL;	//Final markup to echo.
		$cLabel		=	NULL;	//Item label markup.
		$cIDFinal	= 	NULL;	//Final ID inserted into markup.
		$cEvent		=	NULL;	
		
		$cMarkup .='<div class="'.$cClass['Container_All'].'">';
		
		/* Set ID to name? */
		$cID = $cID != self::FORMS_ID_USE_NAME ? $cID : $cName;
		
		/* If current value empty or NULL, set "No current" cosntant */
		$cCurrent = $cCurrent ? $cCurrent : self::FORMS_VALUE_CURRENT_NONE;
		
		/* Parse event actions. */
		$cEvent = $this->forms_events_markup($cEvents);		
		
		foreach ($cList as $cKey => $cValue)
		{				
			/* If $cValue matches $cCurrent, or $cCurrent not provided but $cValue matches $cDefault, add 'checked' to make this the default list item selected. */	
			if ($cCurrent == $cValue || ($cCurrent == self::FORMS_VALUE_CURRENT_NONE && $cValue == $cDefault))
			{
				$cChecked = 'checked="checked"';
			}
			else
			{
				$cChecked = NULL;
			}		
					
			/* IDs must be unique, so we'll combine ID with value. */
			$cIDFinal = $cID."_".$cValue;			
			
			/* Prepare label markup. */
			$cMarkup .= $this->forms_label_markup($iLabelStyle, self::FORMS_LABEL_TYPE_FIELDSET, $cName, $cIDFinal, $cKey, $cValue);			
			
			$cMarkup .= '<div class="'.$cClass['Container_Item'].'"><input type="radio" name="'.$cName.'" id="'.$cIDFinal.'" value="'.$cValue.'" '.$cChecked.' '.$cEvent.' /></div>';			
		}					
		
		$cMarkup .= '</div>';
		
		/*
		Return end result.
		*/
		return $cMarkup;
	
	}
	
	public function forms_select($cName=NULL, $cID=self::FORMS_ID_USE_NAME, $iLabelStyle=self::FORMS_LABEL_USE_ITEM_KEY, $iKeyStyle=self::FORMS_LABEL_USE_ITEM_KEY, $cList=array(NULL => NULL), $cDefault=NULL, $cCurrent=self::FORMS_VALUE_CURRENT_NONE, $cClass=array("Item" => "Standard", "Container_All" => "Show Me"), $cEvents=NULL)
	{
		/*
		forms_radio
		Damon Vaughn Caskey
		2013-03-24	
		*/
								
		$cKey		=	NULL;	//Array key.
		$cValue		=	NULL;	//Array value.
		$cChecked	= 	NULL;	//Checked (default/current)?
		$cMarkup 	=	NULL;	//Final markup to echo.
		$cLabel		=	NULL;	//Item label markup.
		$cEvent		=	NULL;	
		$cItems		=	NULL;	//Select options.
		
		$cMarkup .='<div class="'.$cClass['Container_All'].'">';
		
		/* Set ID to name? */
		$cID = $cID != self::FORMS_ID_USE_NAME ? $cID : $cName;		
		
		/* Parse event actions. */
		$cEvent = $this->forms_events_markup($cEvents);		
		
		if (!is_array($cList))
		{
			$cList = array(NULL => NULL);
		}
		
		foreach ($cList as $cKey => $cValue)
		{				
			/* If $cValue matches $cCurrent, or $cCurrent not provided but $cValue matches $cDefault, add 'checked' to make this the default list item selected. */	
			if ($cCurrent == $cValue || ($cCurrent == self::FORMS_VALUE_CURRENT_NONE && $cValue == $cDefault))
			{
				$cChecked = 'selected';
			}
			else
			{
				$cChecked = NULL;
			}		
					
			/* Prepare label markup. */
			$cLabel = $this->forms_label_markup(self::FORMS_LABEL_USE_ITEM_KEY, self::FORMS_LABEL_TYPE_INLINE, $cName, $cID, $cKey, $cValue);	
			
			$cItems.='<option value="'.$cValue.'" '.$cChecked.'>' . $cLabel . '</option>';			
		}				
		
		
		/* Prepare label markup. */
		$cLabel = $this->forms_label_markup($iLabelStyle, self::FORMS_LABEL_TYPE_FIELDSET, $cName, $cID, $cKey, $cValue);	

		$cMarkup .= $cLabel.'<div class="'.$cClass['Container_Item'].'"><select type="select" name="'.$cName.'" id="'.$cID.'" class="'.$cClass['Item'].'" '.$cEvent.'>'.$cItems.'</select></div></div>';
		
		/*
		Return end result.
		*/
		return $cMarkup;
	
	}
					
	public function forms_text($cName=NULL, $cID=self::FORMS_ID_USE_NAME, $cLabel=self::FORMS_LABEL_NONE, $cDefault=self::FORMS_VALUE_DEFAULT_NONE, $cCurrent=self::FORMS_VALUE_CURRENT_NONE, $cClass=array("Item" => "Standard"), $cEvents=NULL)
	{	
		/*
		forms_text
		Damon Vaughn Caskey
		2013-01-21
		
		Output form text input markup.				
		*/
	
		$cMarkup 	=	NULL;	//Final markup to echo.
		$cEvent		=	NULL;	//Event string.	
		
		$cMarkup .='<div class="'.$cClass['Container_All'].'">';
				
		/* Set ID to name? */
		$cID = $cID != self::FORMS_ID_USE_NAME ? $cID : $cName;
		
		/* If current value empty or NULL, set "No current" cosntant */
		$cCurrent = $cCurrent ? $cCurrent : self::FORMS_VALUE_CURRENT_NONE;
		
		$cDefault = $cDefault == self::FORMS_VALUE_DEFAULT_NONE ? NULL : $cDefault; 
		
		/* If no current value is available, use default. */
		if(!$cCurrent || $cCurrent == self::FORMS_VALUE_CURRENT_NONE)
		{
			$cCurrent = $cDefault;
		}
				
		/* Parse event actions. */
		$cEvent = $this->forms_events_markup($cEvents);			
		
		/* Prepare label markup. */
		$cMarkup .= $this->forms_label_markup($cLabel, self::FORMS_LABEL_TYPE_FIELDSET, $cName, $cID, $cKey, $cValue);
					
		$cMarkup .= '<div class="'.$cClass['Container_Item'].'"><input type="text" name="'.$cName.'" id="'.$cID.'" class="'.$cClass['Item'].'" value="'.$cCurrent.'" '.$cEvent.' /></div></div>';
		
		/*
		Return end result.
		*/
		return $cMarkup;
	}
	
	public function forms_time($cName=NULL, $cID=self::FORMS_ID_USE_NAME, $cLabel=self::FORMS_LABEL_NONE, $cDefault=NULL, $cCurrent=NULL, $cOptions="{dateFormat: 'yy-mm-dd', timeFormat: 'HH:mm:ss', controlType: 'select', changeYear: true, constrainInput: true}", $cFunction="datetimepicker", $bReadOnly=self::FORMS_READ_ONLY_ON, $cClass=NULL, $cEvents=NULL)
	{	
		/*
		forms_time
		Damon Vaughn Caskey
		2013-01-21
		
		Output form date/time input markup.
		*/
	
		$cMarkup 	=	NULL;	//Final markup to echo.
		$cEvent		=	NULL;	//Event string.	
				
		$cMarkup .='<div class="'.$cClass['Container_All'].'">';
				
		/* Set ID to name? */
		$cID = $cID != self::FORMS_ID_USE_NAME ? $cID : $cName;
		
		/* If current value empty or NULL, set "No current" cosntant */
		$cCurrent = $cCurrent ? $cCurrent : self::FORMS_VALUE_CURRENT_NONE;
		
		$cDefault = $cDefault == self::FORMS_VALUE_DEFAULT_NONE ? NULL : $cDefault; 
		
		/* If no current value is available, use default. */
		if(!$cCurrent || $cCurrent == self::FORMS_VALUE_CURRENT_NONE)
		{
			$cCurrent = $cDefault;
		}
		 						
		/* Parse event actions. */
		$cEvent = $this->forms_events_markup($cEvents);			
		
		/* Prepare label markup. */
		$cMarkup .= $this->forms_label_markup($cLabel, self::FORMS_LABEL_TYPE_FIELDSET, $cName, $cID, $cKey, $cValue);		
		
		$cMarkup .= "<script>$(function(){
						$( '#".$cID."' ).".$cFunction."(".$cOptions.");
					});</script>";

		$cMarkup .= "<input type='text' name='".$cName."' id='".$cID."' value='".$cCurrent."'";
		
		if ($bReadOnly==TRUE)
		{
			$cMarkup .= " readonly='readonly'";
		}
		
		$cMarkup .= " /></div>";
		
		/*	Return end result. */
		return $cMarkup;
	}
	
	public function forms_fieldset($cID=NULL, $cLegend=self::FORMS_LEGEND_NONE, $cElements=array(NULL), $cAddOns=array(NULL), $cClass=array("Div"=>"table_row_0", "Fieldset"=>"Standard"))
	{
		/*
		forms_fieldset
		Damon Vaughn Caskey
		2013-03-25
		
		Arrage form input items into a fieldset.
		
		$cID: 		Fieldset ID.
		$cLegend: 	Legend label, if any.
		$cElements:	Markup of field list items (select lists, radio groups, text boxes, etc).
		$cClass:	Style classes.
		*/
		
		$cElementKey	= NULL;	//Single element key.
		$cElementVal	= NULL;	//Single element value.
		
		$cMarkup = '<div class="'.$cClass['Div'].'"><fieldset id="'.$cID.'" class="'.$cClass['Fieldset'].'">';
		
		
		if($cLegend != self::FORMS_LEGEND_NONE)
		{
			$cMarkup .= '<legend>'.$cLegend.'</legend>';	 
		}				
		
		foreach($cAddOns as $cElementKey => $cElementVal)
		{
			$cMarkup .= '<div class="'.$cElementKey.'">'.$cElementVal.'</div>';
		}	
		
		foreach($cElements as $cElementKey => $cElementVal)
		{
			$cMarkup .= '<div class="'.$cElementKey.'">'.$cElementVal.'</div>';
		}		
		
		$cMarkup .= '</fieldset></div>';
		
		$this->cFieldset[$cID] = $cMarkup;
		
		return $this->cFieldset[$cID];
	}
		
}
?>

Class – Config

<?php	
	
	/*
	Configuration file. This should be added to all PHP scripts to set up commonly used includes, functions, objects, variables and so on.
	*/
	
	$iReqTime 	= $_SERVER['REQUEST_TIME_FLOAT'];
	$cDocroot 	= NULL; //Document root.	
	$oDB		= NULL;	//Database class object.
	$oErr		= NULL;	//Error class object.
	$oMail		= NULL;	//E-Mail handler class object.
	$oSes		= NULL;	//Session handler class object.
	$oUtl		= NULL;	//Utility class object.
	$oFrm		= NULL;	//Forms class object.
		
	/* Get needed includes. */
	require_once("access.php");		//Account based access.
	require_once("constants.php");	//Global constants.
	require_once("database.php");	//Database handler.
	require_once("forms.php");		//Forms handler.
	require_once("error.php");		//Error handler.
	require_once("mail.php");		//Mail handler.
	require_once("sessions.php");	//Session handler.
	require_once("tables.php");		//Table handler.
	require_once("utility.php");	//Utility functions.	
		
	/* Initialize class objects */
	$oUtl	= new class_utility();														//Utility functions.
	$oMail	= new class_mail();															//E-Mail handler.
	$oErr 	= new class_error(array("Mail" => $oMail, "Utl" => $oUtl));					//Error handler.
	$oDB	= new class_db(array("Utl" => $oUtl, "Err" => $oErr));						//Database handler.
	$oSes	= new class_sessions(array("DB" => $oDB));									//Session handler.	
	$oAcc	= new class_access(array("DB" => $oDB, "Ses" => $oSes, "Utl" => $oUtl));	//Account based access.
	$oTbl 	= new class_tables(array("Utl"=>$oUtl));									//Tables handler.
	$oFrm 	= new class_forms(array("DB"=>$oDB));										//Forms handler.
		
	$cDocroot = $oUtl->utl_get_server_value('DOCUMENT_ROOT')."/";
	
	/* Replace default session handler. */
	session_set_save_handler($oSes, true);
	
	/* If session not started, initialize. */
	if(session_status()==PHP_SESSION_NONE)
	{					
		session_start();
	}
?>

Script Catalog

NOTICE (Updated 2018-02-23):

I no longer host scripts from this blog – they are now located at my github repository. Most of the scripts below have broken download links and nearly all of them are out of date. I am in process of re-writing them to match the latest OpenBOR engine updates.

In the list below, you will find several items already linked to the library that replaced them. If you do not see a replacement link, I would suggest browsing my my github – you will likely find what you’re looking for.

Thanks,
DC

 

Update log for common script and functions used by all modules. I have found that I often forget which script from identical working copies is the newest version. This should help track them.

License

A copy of or (preferably) link to this license is required for use of the materials below or any other resources provided by Damon Caskey.

 


Event Scripts

Self contained scripts run immediately in response to an engine event. Most will be used to call a user custom defined function.

z_ani

Generic animationscript event script.

z_blank

Blank script file. Allows @cmd tags with entities that do not need user defined functions.

z_block

Universal didblockscript event script.

z_blocka

Universal onblockascript event script.

z_datk

Universal ondoattackscript event script.


Functions

Custom predefined functions to be called by event scripts.

ani0009

Animation switching wrapper. Validates animation before a switch attempt is performed and allows quick use of both available animation switch methods. Handles basic animation switching needs for almost every other function.

ani0012

Specialized animation switch; performs Grabattack2 if opponent is “Bad Brother”. I normally prefer more a more generic approach using parameters, but this is in place already and it does do the job.

Moved to Avalanche repository.

ani0014

If target can be grappled, caller is set to passed animation. Otherwise alternate animation is used if available. If not, nothing is done.

ani0016

Perform alternate animation if target is rising or performing a rise attack.

ani0020

Wrapper to perform animation when triggered by key script.

bind0010

Primary grapple binding function. Binds and sets target entity into desired pose, or sets position and releases.

bind0017

Find preexisting entity with same name and warp to location. Primary use is to spawn self on top of old corpse. Creates effect of a “dead” character standing back up when player continues or has lives remaining; much less jarring then a new copy appearing out of nowhere.

bind0021

Shunt self away from wall if bound entity is inside of it (wall). Prevents getting a bound entity stuck within areas meant to be inaccessible or bounced to infinity by wall functions.

dama0004

Damage caller’s bound entity by index. Differs from dama0001 in that a mode is provided to allow non-lethal damage, and attack types are passed as integers rather than a string.

  • 2010_02_02Fatal Fury Chronicles
    • Authored.
  • 2011_07_19Golden Axe The Hackjob
    • iDam variant now initialized as 0. It seems OpenBOR will occasionally keep the value of a NULL initialized variant in a given script across multiple callings. Such a bug would be virtually impossible to find and eliminate, especially given its inconsistency; it is more pragmatic to simply adapt scripts accordingly. In the case of dama0004, retention of an iDam value of -1 could cause targets to have their hit points reduced to 0 immediately. In turn applied damage would always be lethal regardless of intended amount.

debug0001

A very messy but (to me) invaluable function to view and modify various system values in game. Its most powerful use is to edit grapple move poses during live game play and log the resulting command for use in a model text.

draw0001

Primary setdrawmethod() wrapper. Enables the classic pseudo 3D scaling and rotation effects when entities move about the screen as in many golden age arcade games, particularly the Neo-Geo.

draw0002

Support function for draw0001. Applies ratio parameter to offset parameter, ensuring the result is always a whole number. Main use is to integrate zoom effect with binding (bind adjustments must be whole numbers).

draw0003

Assign a group of draw method parameters to model, to be applied by draw0001.

ht0001

Returns entity’s current height, including current draw adjustment.

jump0003

Enable double jumping, wall jumping, etc.

Replaced by Hansburg library.

jump0005

Enable Mario Brothers style jump height control.

Replaced by Hansburg library.

mode0001

Wrapper to switch entity’s weapon and model name.

mode0002

Find out what weapon models (if any) players have and store them for placement into next level. Originally written to enable the feature in Golden Axe where an animal was retained from previous level, only to automatically run away during bonus stage.

  • 2011_03_29Golden Axe The Hackjob
    • Copied directly from “saveanimal” by Utunnels (contains a previous modification by myself to accommodate Joe & Alex). Will likely need a rewrite to take advantage of new string functions; it currently uses a limited static list and I am enabling playability for nearly every character in the game.

parr0001

Parry functionality. Check for Key timing and apply parry settings accordingly.

path0001

Returns full path of passed entity, minus file name.

path0002

Returns folder of passed entity.

rnd0001

Generate random numbers.

Replaced by Eggball library.

run0001

Simple “stop running” function useful for adding a controllable dash movement.

soun0005

Sound catalog and player. Catalogs sounds by tiers and selects sound from group at random. :

  1. entity/sounds folder
  2. global sounds/throwdamage
  3. global sounds

May also play specific sound from group if needed.

soun0009

Sound effect wrapper. Verify sample and last play time, then play sample with location based stereo effect.

velo0001

Wrapper for velocity control.

wall0001

Primary wall function. Damages and bounces self in opposite direction of current X velocity when falling and obstructed by wall/platform. Also provides optional jumping/climbing onto an obstacle along Z axis for AI controlled entities. OpenBOR already allows optional Z jumping, but AI controlled entities always use their full jump height/length and may overshoot if the target platform is small. There are also cases where a custom animation would simply provide greater aesthetics.

  • 2009_10_10 – Fatal Fury Chronicles
    • Authored (wall bounce only). Optional climbing added later (unknown date).
  • 2011_07_22Golden Axe The Hackjob
    • Wall bounce velocity is now a % of original velocity instead of a static value. This adjustment adds nuance to the timing needed for wall juggles. It also improves visual appeal, as a harder impact is now rewarded with a harder bounce. Additionally, victims caught between two walls will no longer infinitely bounce and “climb” upward; the gradual loss of velocity means they will eventually lose momentum and fall as expected.

z_block

Universal ondidblock function.

z_datk

Universal ondoattack function.

z_spawn

Universal onspawn event function.

Work Log

This is a general work log for my Fatal Fury Chronicles module project. The goal is to replicate game play elements found within SNK’s Fatal Fury series in a side scrolling Final Fight style environment, while presenting the back story in a more cohesive layout. At the start of this log (11292010) the module is in a playable beta state.

11292010

  • Start of work log.
  • Project back under way after being idle for approximately one year.
  • Resolved issue of crashing instantly during menu on newer revisions. Issue turned out to be nothing more then a leftover configuration save file. Module is in beta status; it has one fully playable stage and one test area, but I would like to add more content before releasing any kind of demo.

11302010

  • Andy Bogard now uses randomized sound effect system. See soun0005() function.

12012010

  • Merge old MS Word based work log.

12022010

  • Andy Bogard model scripts converted to latest convention.
  • Terry Bogard model scripts converted to latest convention.
  • AI Ray McDougal model scripts converted to latest convention.
  • Bug fixes.

12032010

  • Cut JUMPDELAY from 18 to 8; too much of a pause before jump executes.
  • Parked cruisers in Sound Beach stage now catch fire immediately when damaged (previously not until destroyed) and sink with scripted velocities instead of offset/base manipulation.

12052010

  • Added entity verify to draw0001(). Previously it would cause immediate shutdown if called on non existent entity.
  • Andy Bogard & Raiden sprite files converted to nameless serial convention.
  • Bug fixes.

12062010

  • All models and sprites in data/misc/effects updated to serial name convention.

12072010

  • Function bind0010() merged from Golden Axe Hackjob.
  • Function dama0004() merged from Golden Axe Hackjob.
  • All models and sprites in data/misc/flash updated to serial name convention, cataloged in master sprite database and moved to data/misc/effects.
  • All models and sprites in data/misc/dust updated to serial name convention, cataloged in master sprite database and moved to data/misc/effects.

12082010

  • Terry Bogard and Ray sprites updated to nameless serial convention. This completes update of all primary models to date.
  • Latest draw0001() and it’s support functions now use target’s current alpha setting instead of an entity var to get and store the blend parameter. Exact same functionality, but with one less wasted variable and one less script parameter to worry about initializing when entities are spawned. I should have spotted it two years ago.
  • Flashes use MP as a sound constant in place of if/list based on alias.
  • Flashes use Nodrop property as random rotation range.
  • All previously uncatalogued sounds removed from main sound folder. Model specific sounds moved to their respective owners still need cataloging.

12092010

  • Script removal. Several spawn scripts are no longer needed; they basically performed the same function with minor differences. Their functionality is now consolidated into spaw0002.c with sound cataloging and free variable application.
    • spaw0003.c
    • spaw0006.c
    • spaw0007.c
    • spaw0008.c
    • spaw0009.c
  • Ray and Raiden’s sounds cataloged. Both still need sound calls upgraded to soun0005.c function.
  • Flash spawning function created (bind0022()).

12102010

  • Flash spawning fully integrated. Required new functions (summ0002() and bind0023) see below.
  • New function: summ0002(void <vEnt> char <cModel> char <cAlias> int <iMap> int <iBlend> int <iX> int <iY> int <iZ> int <iDir> int <iAni> int <iFrame> int <iKill>)

    • Spawns an entity (usually a flash) by name unless <vEnt> has a custom flash defined by the flash model header command, in which case the defined custom flash will be spawned. Location is screen based.
  • New function: bind0023(void <vTarget> char <cModel> char <cAlias> int <iMap> int <iBlend> float <fAX> float <fAY> int <iAZ> int <iDir> int <iAni> int <iFrame> int <iKill>)
    • Spawns an entity (usually a flash) by name and binds to location of <vEnt>. If <vEnt> has a custom flash defined by the flash model header command, the defined custom flash will be spawned instead.
  • Adjusted block push back (bloc0002(), called in z_hit.c). Previously multiple incoming hits could force the blocking character a ridiculous distance across screen.
  • Merging bind0010() functionality:
    • Andy Bogard
      • Grabbackward
      • Grabforward

12132010

  • Imported key0006() from Golden Axe Hackjob and finished updating keyall.c to latest standard. Had missed these earlier; debu0001() cannot work without them.
  • Added font3.png (it’s a copy of font2 for now). Not normally needed in game, but is used by debu0001().
  • Removed final frame from Andy Bogard’s taunts. Idle animations already include transition frames.

12142010

  • Further progress for “bind to target” capability in bind0010(). Only needs velocity application.

12152010

  • debu0001() now includes ability to toggle autozoom from draw0001().
  • key0006() given new parameter <fQnt>. Allows increments/decrements by specified quantity instead of static 1.
  • bind0010() modes rearranged (in progress). See function.

12162010

  • New function: ani0012(void <ent>, int <iH>, int <iFrame>)
    • Skips to <iFrame> if target’s Y axis position is <= <iH>. Allows jump animations to adapt to variable height (i.e. don’t start a full somersault during a short hop).

12172010

  • Taunt/Poses now mapped to Special + Attack + (optional) Direction instead of Pose + (optional) Direction. This cuts action keys back to Attack, Jump, Special, and Backstep. A fifth action key dedicated to poses just seemed cumbersome and didn’t mesh well with the overall control scheme.
  • bind0010() modifications complete. Now accommodates binding based on attacker or defender’s location and automatic adjustments to deal with larger/smaller characters without switching animations.

12182010

  • Separated Work Log from Bug List/Notes. It was getting unwieldy to sift through, and separation will also allow tabbing between “to do” and current progress rather then scrolling. The Bug List/Notes article will probably need breaking down later as well.
  • Updated dama0002() function:
    • Grapple binds are now released correctly when taking damage.
  • Updated pain0001() function:
    • Hit level animation switching now verifies alternate pain animation before applying.
    • Application of hit effects. See effe0001() function.
  • Updated fall0001() function:
    • Hit level animation switching now verifies alternate fall animation before applying.
    • Individual checks broken down to separate if calls for readability.

12192010

  • Updated kill0001() function.
    • draw0001() no longer called to reset parent map for effect models (not needed as draw0001() is not used for standard mapping).
    • Entity variable cleaning code replaced with vars0001() function.
  • Updated spaw0002() function.
    • Entity variable cleaning code was missing. Replaced with vars0001() function. Shouldn’t be needed since cleaning is done on entity removal by kill0001(), but adding it anyway in case an unforeseen engine glitch does not allow kill0001() to finish.
    • Random name code replaced with name0001() function (function is identical to original name code).
  • Updated fall0001() function:
    • Application of hit effects. See effe0001() function.
  • Adjusted Ray’s Burn animheight accommodate burn effect.
  • Added SPAIN and SHOCK animation to Ray; shock effect works as designed.
  • On death fatality adjustments in progress. Is komap setting open to script?

12232010

  • New function: draw0006(void <vEnt> void <vMatch>)
    • Enables matching of draw scale between two entities using their height settings. Replaces the “quick and dirty fix” that was in kill0001() for almost two years. Previously the fatality model spawned on burn death was given a static size adjustment of 75% as its sprites were over sized; the adjustment is now automated. An obvious added benefit is that it will also self adjust to match abnormally sized characters with any further amendments.
  • Replaced burn fatality sequence with ball explosion and Komap switching. See kill0001(). A better solution would be to turn Komap off, but due to the engine’s layout this is not yet possible. An engine modification is needed to fix this issue long term, but in game everything works as needed.
    • Look into using hmap property; if available to script, will remove need for static map constant when resetting Komap.
      • 12262010 – property open to script and can be used to replace static map constant as described.

12242010

  • For whatever reason stupidity on my part jump0005() function was embedded in z_movez.h. Function moved to its own file.

12252010

  • Setting for komap in deat0001() now uses hmapl property in place of MAPX constant.
  • Function soun0003() (random KO sounds) removed. Functionality long since replaced by soun0005() and numeric based sound cataloging.

12262010

  • Variables iRemap and iAlpha removed from draw0001() function. There is no need for them as the passing -1 to either as a parameter in setdrawmethod() allows target entity to use its own property setting. Previously setdrawmethod() would override entity’s map and alpha properties, meaning they had to be adjusted through draw0001().
  • New function: targ0002(void <vEnt> int <iAni> int <iType> int <iSType> int <iTD>)
    • Find nearest entity within range of a base entity’s animation range setting that matches type, subtype and throwdamage parameters. Intended use is to replace item based music change and similar Easter eggs, but is useful for finding any specific entity not otherwise detectable with global findtarget() function.
    • Step 1 for replacing item based music switching; detects when user presses key in close proximity to music switching trigger obstacle.
  • Script removal:
    • dust0001.h
      • Was a name based spawn function only used by Sound Beach cruiser to spawn water splash. Water splash can be replicated by another function. General use for script has otherwise long since been replaced by my dust model header additions.
    • dust0002.h
      • Similar to dust0001.h but with numerous parameters added. Functionality similarly replaced by recent additions to engine’s default dust system and was not in use at all.

12282010

  • Andy Bogard’s palette sprites moved to <char>/pals folder and converted to nameless serial. Palette 0015 (black) retouched to match KOF2000.
  • New function: file0001(int <iVar> char <cFile> char <cRow> int <iColumn>)
    • Wrapper for basic text file reading; finds row by text passed through <cRow> and returns value located at column number <iColumn>.
    • Step 2 for replacing item based music switching. Will locate music by by being passed music switch obstacle’s <aggression> value. Desired column will be passed as looping numeric cycle (not complete at this time).

12292010

  • New function: musi0001(void <vEnt> int <iNotch>)
    • Locates text file argument by using target entity’s aggression setting as row search and nodrop as column number. Argument is passed to global playmusic() command and <iNotch> is added to nodrop setting to determine column selected next time function is called on target entity. This allows use of obstacle as an interactive switch in general instead of music only. Furthermore, as aggression is a spawn parameter, the same item can be used for multiple sets of music with no modifications, variables, or additional scripts. Additionally, soun0005() is activated on use, passing the obstacle’s maximum health as sound group. In the same manor as aggression setting, adjusting the heath spawn parameter allows choice of sound effect group without any adjustments to model/scripts.
    • Step 3 for replacing item based music switching; will be added to global keyall.c script.
  • New entity: data/chars/misc/pickups/swit0001
    • Is an inert obstacle made to replace item based music switching and other types of stage interactivity as noted above.
  • Sound effects 100_0 and 100_1 (both are radio tuning sound effects) added, and moved to /swit0001/sounds folder.
  • Music switching upgrade complete.

12312010

  • New script: z_datk
    • Called by most entities as their ondoattack script.
  • Script updates:
    • parr0001:
      • Retool in progress to take advantage of engine features available as of version 2.955.
        • Parry animation.
        • Air Parry animation.
        • Pause for Defender and Attacker
        • Switch direction to face Attacker.
        • 30% MP recovery for Defender.
        • Engine default collision disabled.
      • Remaining:
        • Parry flash.

          • 01012011
        • On screen alert?
        • Adjust timing threshold for parry command? Current timing of 0.3 seconds seems too easy at the moment, but test was in controlled environment with single opponent using a predictable attack.

          • 01012011
  • Raiden:
    • Added Drop Kick.

01012011

  • New Entities:
    • bflash: Block flash.
    • pflash: Parry flash.
    • fflash: Fire flash.
    • eflash: Electric flash.
    • iflash: Indirect flash.
  • All flash models now located in folder data/chars/misc/effects/models/flash.
  • Script updates:
    • parr0001:
      • Parry flash.
      • Check for state (can’t be in pain or blockstun).
      • Command mapping finalized: Press Special within 0.3 seconds or less before incoming attack makes contact.
      • On screen prompt?
  • Ray:
    • Old sound calls replaced with soun0005(). Now fully upgraded to self cataloging random sound system.
    • Aggression speed increased from 130 to 0.
    • Shock fatality.
  • Andy Bogard:
    • Minor bbox correcting in BLOCK animation.

01022011

  • Andy Bogard
    • Additional voice effects.
      • 3_0
      • 3_1
      • 3_2
      • 7_4
      • 26_2
      • 27_1
      • 28_2
      • 28_2
      • 103_1 (Geki Hishoken 2)
      • 111_0 (“hatz”)

01032011

  • Andy Bogard
    • Additional voice effects:
      • 110_1 (Zaneiken 2)
      • 112_0 (“zan”)
      • 113_0 (Speaking to Tung Fu Rue)
      • 114_0 (“Master Tung”)
      • 115_0 (“Geese”)
      • 116_0 (“san”)
      • 117_0 (Max attack unleash)
  • New entities:
    • Hyper sparks:
      • Hyper sparks separated back into individual models. Previously a single model (paus0001) contained animations and palettes for all hyper effects. The idea was to reduce memory consumption. While text files are minimal, each model loads its own copy of any given script. However, by separating each into their own models, the sound cataloging system can be implemented in the same manor as normal models (less confusion). Furthermore, this eliminates need for animation scripts within the hyper spark models; only a one time spawn script is needed. The overall result will be slightly greater memory consumption, but with better run time performance. The latter is of greater concern as this module will not run on a memory shy platform anyway due to sheer quantity of animation.
        • hype0001
        • hype0002
        • hype0003
  • New sound files:
    • /hype0001/11_0
    • /hype0002/11_0
    • /hype0003/11_0

01062011

  • Revamp of projectile system in progress to take advantage of ondoattackscript:
    • Projectile models are to be separated back into individual models (previously they were combined, see hyper sparks in 01032011 entry).
    • Current projectile function proj0002 to be greatly simplified, it currently requires 33 parameters.
    • Addition of numeric indexed global vars (see grapple binding) to track all projectiles?
    • Engine modification:
      • Script access to throwframewait property. It is another excellent candidate for Free Variable use.
      • Script access to custom attack flash. Will greatly simplify and improve efficiency of spawning custom flash entities.

01112011

  • New function: debu0002 void <vEnt>
    • Uses script access of attack/bbox properties to display collision boxes during gameplay.
    • Includes temporary sub function shap0001() to perform draw actions.
  • Model updates:
    • Andy Bogard:
      • Updated collision boxes using new debug capability for following animations:
        • attack2
        • attack3
        • attack4
        • attack5
        • attack6
        • attack7
        • attackbackward
        • attackdown
        • attackup
        • block
        • blockpain
        • blockpain3
        • blockpain4
        • bpain
        • burn
        • cant
        • charge
        • chargeattack
        • death
        • duck
        • faint
        • fall
        • fall3
        • fall5
        • fall6
        • fall7
        • fall8
        • fall9
        • follow1
        • follow4
        • follow5
        • follow6
        • follow7 (switched to editor)
        • follow70
        • follow71
        • follow72
        • follow8
        • follow80
        • follow81
        • follow82
        • follow83
        • follow84 (Sidestep Up attack)
        • follow85
        • follow86
        • follow87
        • follow88 (check purpose)
        • follow89
        • follow92
        • forwardjump
        • freespecial
      • Consolidated death animations; removal of following:
        • death11
        • death6
        • death7
        • deaht8
      • Counter block impact remnant removal (long since replaced with dodge and parry).
        • follow2
      • Tall target alternate for Shiranui Spider Clutch removed. Updated bind allows auto adjusting to opponent’s height.
        • follow3

01122011

  • Model updates:
    • Andy Bogard:
      • Updated collision boxes using new debug capability for following animations:
        • freespecial10  (Shoryudan) *LF
        • freespecial12 (Zaneiken)
        • freespecial13 (Shippu Oken)
        • freespecial14 (Gen’ei Shiranui)
        • freespecial15 (Gen’ei Shiranui – Shimo Agito)
        • freespecial16 (Gen’ei Shiranui – Uwa Agito)
        • freespecial17 (Yami Abse Geri)
        • freespecial18 (Cho Reppa Dan)
        • freespecial19 (Bakushin)
        • freespecial2 (Plane Force Up)
        • freespecial21(Ku Bakushin)
        • freespecial3 (Plane force Down)
        • freespecial4 (Plane shift attack up)
        • freespecial40 (Low Step Attack)
        • freespecial41 (Low Dash Attack)
        • freespecial42 (Low Step Attack II – Low side kick)
        • freespecial43 (Low Step Attack III – Low palm strike)
        • freespecial5 (Plane shift attack Down)
        • freespecial6 (Hishoken)
        • freespecial7 (Geki Hishoken)
        • freespecial8 (Ku Ha Dan)
        • freespecial9 (Zaneiken)
        • get
        • grab
        • grabattack
        • grabattack2
        • grabbed
        • grabup
        • idle
        • idle2
        • idle3
        • idle4
        • jump
        • jumpattack
        • jumpsttack2
        • jumpsttack3
        • jumpdelay
        • jumpforward
        • jumpland
      • Low Dash Attack is now shoulder ram. Old low dash attack (a lunging low palm strike) will be used for SDM attack.

01132011

  • Model updates:
    • Andy Bogard:
      • Updated collision boxes using new debug capability for following animations:
        • pain
        • pain2
        • run
        • runattack
        • runjump
        • runjumpattack
        • runslide
        • slide
        • special2
        • walk
      • Sidesteps now have invincible start up.
    • Ray
      • Updated collision boxes using new debug capability for following animations:
        • attack1
        • attack2
        • attack3
        • attack4
        • attackbackward
        • attackdown
        • attackup
        • blockpain
        • blokcpain2
        • blockpain3
        • blockpain4
        • bpain
        • burn
        • chargeattack (wheel kick)
        • fall
        • fall2
        • fall3
        • fall5
        • fall8
        • fall9
        • slide
      • Renamed follow40 to Slide.

01172011

  • Color separation in progress for Ray model. Original model used 16 color sprites with white as a highlight and inappropriate color sharing. New color layout provides 64 colors, separated into individual groups for hair, skin, shirt, pants, gloves and shoes.
    • Complete:
      • aapa0000
      • aapa0011
      • 0001 through 0035

02092011

Free Variables

List of outdated or superfluous model and global level values used as script parameters in place of their intended purpose. The idea is simple enough: The variables exist in the engine anyway, so why not save a kilobyte or two and use them instead of adding more to a script?

*Used as normal otherwise.

  1. MP*
    • Projectile priority for obstacle types. See prjo0001() function.
  2. Throwdamage
    • Custom Subtype:
      • 0 = Human Male.
      • 1 = Human Female.
      • 2 = Mechanical.
      • 3 = Music change.
      • 21 = Default value.
  3. Speed*
    • Manual zoom adjustment for flash effects. See spawn0002.c script.
  4. Alpha*
    • Function draw0001() reads in entity’s current alpha setting and uses it as alpha/blend parameter when applying settings to setdrawmethod().
      • 12262010 – Removed. No longer needed as passing -1 to the alpha parameter of setdrawmethod() allows entity’s current alpha property to be used instead.
  5. Nodrop*
    • Flash effects pass random number from 0 through <nodrop> to ADROTATE (results in single random rotation on spawn).
  6. Flash
    • If <Noatflash> parameter is set ( != 0), Flash effects will spawn <Flash> model as a secondary effect. If <Noatflash> is not -1, the secondary model will have its animation set to follow+<Noatflash>. See spaw0002.c for details.
  7. Guardpoints
    • Parameter to identify and differentiate one shot entities (flash & dust) from each other. This allows a single spawn script to deal with most one shot entities and still treat them uniquely (i.e. a dust entity uses a splash animation when out of Z bounds in a beach stage, but a flash entity does not).
  8. Nodrop
    • Music Switching
      • Music text file stream column position marker. See musi0001() function.
  9. Aggression
    • Music switching
      • Music text file stream row position marker. See musi0001() function.
  10. Max Health
    • Music switching
      • Sound effect to play when music switch takes place. See musi0001() function.

Notes & Bug List

This is a general work log for my Fatal Fury Chronicles module project. The goal is to replicate game play elements found within SNK’s Fatal Fury series in a side scrolling Final Fight style environment, while presenting the back story in a more cohesive layout then the original. At the start of this log (11292010) the module is in a playable beta state.

Known Bugs/Issues:

Major

  • Andy Bogard’s shadow trails are not palette matched during certain jump frames. This means sprite set as a whole needs re mastering to synchronize palette.
    • 11302010 – All palettes are correct. Problem seems to be specific sprites are corrupted:
      • 0044
      • 0045
      • 0046
      • 0171
      • 0174
      • 0176
      • 0177
      • 0180
      • 0181
      • 0182
      • 0240
    • 12022010 – Fixed itself. After a year and numerous failed attempts on my part. Go figure.
  • As of OpenBOR revision 2717 the reactive command has been removed due to bugs. This means music changing item in Sound Beach stage is now consumed on use. Possible solution is to replace with inert obstacle that triggers animation and music change via keyscript and proximity.

    • 12292010 – Sound switching now based on keyscript according to proximity to obstacle matching certain parameters. See keyall.c and musi0001() function.
  • Andy Bogard’s Bakushin visual flash has incorrect palette and does not disappear if Andy is interrupted during pose.
    • 12052010 – Updated palette parameter. Failure to disappear and release defender on interrupt is result of a singular script binding Andy to defender. It is a temporary solution that will be replaced by complete throw system from Golden Axe Hackjob.
  • Andy Bogard’s Hishoken caused instant shutdown on hit.
    • 12052010 – Caused by removal of superfluous characters and execution of draw0001(). Andy did not have load command for proj0001. When called, projectile was not spawned since its model had not loaded. When draw0001() was subsequently called on non existent model, the use of setdrawmethod() produced error and shutdown. Fixed by verifying entity in draw0001() and adding load command for proj0001() to Andy.
  • Missing back dash animation for Terry Bogard is causing shutdown. Obviously needs animation added, but also need to roll in newest ani0009 to module; this will verify a given animation before applying it and avoid unexpected shutdowns.
    • 12052010 – Missing animation was actually for “power up”, and added to key script for testing. All known animations intended for game play use have verifying script. Testing line removed.
  • Entities are not getting up more quickly when attacked while down.
    • 12022010 – Staydown values had been adjusted out of playable bounds for testing. Readjusted.
  • Nearly every core script has been updated and/or simplified during Golden Axe Remake project. These scripts need merging to Fatal Fury; will be a difficult undertaking to fundamental differences in modules but ease development in the long run.
    • Use of KO Map feature (reduce variables).
      • 12192010
    • Retrieve and apply default color map without storing it in a variable.
      • 12192010
    • Verifying any animation before application.
      • 12052010
    • Debug system.
      • 12132010
    • Group based random sound effects.
      • 12092010 – Some models still need adapting, but scripts are imported and all sound files cataloged.
    • Grappling system:
  • Lateral auto zoom not working on playable Ray.
    • 12182010 – Playable Ray removed. May be added later using model swapping.
  • Parry system is completely broken. Needs re tooling.
  • Sound Beach stage needs editing at start; palm trees and house appear cut off.
    • 12022010 – Added palm frocks and shorted trunks.

Minor

NA

To Do:

  • Add How To Play images.
  • Remaster music to .ogg format.
  • Random personalities for AI characters.
  • Fatalities
    • Pit.
    • Collapse (normal).
    • Sound Beach Stage.
    • Mr. T.
    • Acid.
    • Shock.
    • Bleed out.
    • Bisect.
    • Decapitation.
    • Incineration
  • Spawn system needs parameters
    • Victory animations.
    • Taunt animations.
    • Others, general.
  • Add Block/Parry flash.
  • Random names
  • Shake effect during grab.
  • Character Specific
    • Andy Bogard
      • Portrait fix.
      • Zaneiken causing instant shutdown.
        • 12022010 -Energycost script property now includes a {parameter} setting. Adding setting.
      • Team attacks
      • Zanei’ Reppa Super
      • Roll, running roll, and rolling attacks.
      • Spider clutch throw needs retuning.
      • Instant shutdown when Cho Reppa Dan KOs opponent.
      • Swept animation.
      • Add dust to CHARGEATTACK animation.
      • Bakushin (“facebomb”) throw, with alternate if opponent is in air.
        • 12012010 – Using wrong voice effect. Visual flash palette incorrect.
    • Ray McDougal
      • Spawn Animations
      • Land sound
        • Covered by land flash
      • Voices.
        • 12012010 – Voices work, but need adapting to latest sound system.
    • Raiden
      • Finish move set.
    • Terry Bogard
      • Finish move set.
      • Rising Tackle causes engine shutdown.
        • 12022010 -Energycost script property now includes a {parameter} setting. Adding setting.
      • Team attacks.
  • Need heavy ground impact flash.
  • Scripts failing after repetitions. Memory leak?
  • Special effects moved to onpain script, need attachment to onfall script as well.
  • Stage Specific
    • Sound Beach
      • Estrada Music item needs longer offscreenkill setting.
      • Sinking boat needs initial sound effect.
      • Panels killed due to lack of offscreenkill setting.
      • Gradual time based sunset should be slower.
      • Boats have too many hit points. Needs destruction animation and prize for doing so.
  • Lasthit x/y/z values are not erased until next real hit. This occasionally messes up throws. Need to add check.
  • Special effects (beams, flame, etc.) are not being zoomed by Z axis on spawn.
  • Proximity hit system needs completing with addition of normal/low/medium/high override support.
  • Add “bounce effect” to screen and obstacles.
  • Flash and dust entities occasionally lock in animation. This is an engine bug; add lifespan as a fail-safe.
  • Finish Taunting system.
    • 12012010 – Complete? Original log is marked finished, but I don’t recall doing so. Need to check.
    • Taunting refers to taunting poses, not special effects on opponents. Taunting pose system is in fact completed and only needs the prerequisite animations for playable characters.
  • CHARGEATTACK animation is canceled by alternate idle system. Possible solution would be script access to chargetime.
  • Some special effects no longer use correct remap after 12062010 update to serial name convention. Palette parameters need updating.
  • Create flash spawn function. Currently flash effects during grappling are spawned by generic spawn function. This works, but is somewhat ungainly and does not support custom flash effects certain defenders may use when taking hits.
    • 12092010 – Created bind0022() function.

Work Log:

  • 12182010 – Moved here.

Notes:

Hit Effects (as of 12012010, new system from GA should be merged)

  1. Defender’s onpain & onfall scripts evaluate incoming attack type and any self immunity. If effect is needed, effect function is called. Note this is done here instead of in the takedamage script to accommodate animation forced binding. While takedamage is running, the defender has not yet assumed reactive animation, and effect entity would be killed instantly upon spawn.
  2. Effect function checks BINDHE variable on defender for a previously bound effect entity.
  3. If an effect entity is found in step 2, and is a different model then what would be spawned in step 5, then it is killed.
  4. If an effect is found, but is the same as what would be spawned in step 5, nothing is done and the effect function exits immediately. This prevents pointless spawning/killing and also maintains smooth animation of the effect.
  5. A new effect entity is spawned and bound to caller.
  6. Effect entity’s onkill script clears the defender’s BINDHE variable.

Projectile handling (from old log and script comments. Probably needs separate article):

Default engine projectiles are extreamly limited. Projectiles are therefore scripted to allow following:

  • Single entity acting as different projectiles.
  • All types (enemy, NPC, player, etc) sharing the same projectile command/entity.
  • Projectiles clash and both are destroyed.
  • Projectiles clash and one simply smashes through the other.
  • Projectiles clash and one overpowers the other but is weakened.
  • Projectile is stopped with normal attack.
  • Projectile is hit by normal attack but is not affected.
  • Stationary projectiles/blasts (ex: Andy’s Geki Hishoken).
  • Any combination of the above.
  1. Projectile is obstacle type. It can take hits but is immune to damage. This allows the themselves projectiles be hit, and have the result scripted based on particular needs.
  2. Animation and map is set at spawn. This means a single entity can be used for as many different projectiles as there are maps available.
  3. Candamage is set at spawn. This allows interchangeability between firing of projectile from player, enemy, npc, etc.
  4. Projectile attack box is “no reflect”, “no pause”, “no flash”, and “no damage”. This means unless a reaction is scripted, a hit detection will have virtually no effect.

Directly from script comments:

/*

Since the defender is an obstacle with MP, then it must be another projectile. To simulate projectile priority, we’ll use MP as a free variable.

When two projectiles collide, both must have 2 or more MP, else they will both be unaffected. Otherwise the one with lower MP looses 1HP for each point

of difference. If MP is equal, both take the equal MP amount as HP damage.

0 MP = Only true obstacles have 0 MP.

1 MP = Intangible and has no effect/does not affect other projectiles.

2+MP = Compare. If equal, both take the full MP as HP damage. Otherwise, looser

takes the difference.

Some examples of how to apply this system:

1. Flame (I) vs. Knife (II):

I   = 1MP, 2HP.

II  = 1MP, 1HP.

I & II collide. Both have 1MP, so neither takes any damage from the other.

In Game: A light knife meets a wave of fire, but the fire is immaterial and

so they simply pass through each other.

2. Hadouken (I) vs. Knife (II):

I   = 2MP, 2HP.

II  = 3MP, 3HP.

I & II collide. II has 1 more MP, so I looses the difference (1HP) and is destroyed. II is unaffected.

In Game: A light knife is no match for the Hadouken and gets

knocked cleanly aside while the Hadouken continues unscathed.

3. Hadouken (I) vs. Hadouken (II):

I   = 3MP, 3HP.

II  = 3MP, 3HP.

I & II collide. MP is equal, so both loose 3HP and are destroyed.

In Game: Classic projectile war. Hadoukens hit and negate each other.

4. Hadouken (I) vs. Shinku Hadouken (II):

I   = 3MP, 3HP.

II  = 3MP, 15HP.

I & II collide. MP is equal so both loose 3HP. I is destroyed, II continues

and may withstand 4 more similar collisions.

In Game: Ryu blasts through several enemy projectils with his Shinku Hadouken. It

plows over 4 lesser projectiles and negates head to head with a 5th.

5. Geki Hishoken (I) vs. Hadouken (II):

I   = 20MP, 1000HP

II  = 3MP,  3HP

I & II collide. I has greater MP by 17, so II looses 17HP and is destroyed. I is unaffected.

In Game: Andy’s short range energy attack is effectivly non negatable and easily stops most normal

projectiles while still hitting anyone nearby.

6. Geki Hishoken (I) vs. Haohshokoken (II):

I   = 15MP, 1000HP

II  = 20MP, 20HP

I & II collide. II has greater MP by 5, so I looses 5HP. I easily withstands the loss. II is unaffected.

In Game: Ryo’s Haohshoken and Andy’s Geki Hishoken hit each other with almost no effect. Both

continue unhindered; Andy is almost certain to be hit, but Ryo could be hit as well if he was

close enough to Andy.

7. Haohshokoken (I) vs. Haohshokoken (II):

I   = 20MP, 20HP

II  = 20MP, 20MP

I & II collide. MP is equal, so both loose 20HP. Both are destroyed.

In Game: One Haohshokoken projectile meets another. While both are capable

of overpowering and eliminating most other projectiles, they negate each other.

*/

Attack type handling:

Hit reactions are set using a combination of script and attack type.

  1. When attack hits but does not kill opponent, the appropriate reaction script (onblock/onpain/onfall) will reset the defender’s reaction based on the Y coordinate of impact (see pain/fall in chart) to visually represent an accurate reaction from hit location (face, gut, feet, etc.).
  2. Y coordinate can be overridden with a hitflag (see didhitscript) if desired.
  3. If the attack KOs defender, attack type determines default fatality animation. The resulting fatality may be modified based on Y coordinate, the stage, character and other conditions. See ondeathscript.
  4. Exceptions are attack 6+. If 6 or 7 cause a fall without killing defender, the defender’s animation is NOT reset. This is because resetting a defender’s fall animation does not change the rise type that will be used, and it would be undesirable to change the actual attack type.
  5. 8+ are for special purposes and not used under normal conditions.
Type Pain Fall Death Block Reset to this type (pain/fall/block) condition: Notes
Default: Normal Normal Normal Normal None (default). Bludgeoning (hand to hand, clubs, sword hilts, etc.)
Attack 2 Middle Collapse Bleed out Middle Hit up to 75% of height. Piercing (daggers, arrows, thrown weapons, etc.)
Attack 3 Low Sweep Bisect Low Hit below 30% of height and standing. Slashing (swords, claws, saw blades, etc.)
Attack 4 High Normal Decapitation High Hit 75%+ of height and attacker is airborne.
Attack 5 Normal Flip over end (thrown) Normal
Attack 6 Spin or flip Background fatality
Attack 7 Spin or flip Mr. T fatality
Attack 8 Normal Slammed on head Normal
Attack 9
Attack 10
Shock Shocked Shock Explode Normal
Burn Burned Immolated Incinerated Normal

Sound List (12012010 – no longer relevant due to numeric based sound system; sounds are in conversion process to new naming convention)

Sound List
Generic Sounds
Sound001 Horse Gallop.
Sound002 Soft thump (jump landing).
Sound003 High beep.
Sound004 Electronic “selected” sound.
Sound005 Echo slide (Hanzou slide).
Sound007 Revenge of Shinobi box break.
Sound008 Lighting strike.
Sound009 Liquid spray.
Sound010 NGBC super move start.
Sound011 Blade launch (Hanzou Rekkozan).
Sound012 Loud “raspy” whoosh sound.
Sound013 Electrocution.
Sound014 “Warp/blade”.
Sound015 Fire burst.
Sound016 WH head butt.
Sound018 WH Body Whoosh.
Sound019 Multiple wiffs.
Sound020 Street Fighter 3 super burst.
Sound023 Water splash.
Sound024 Heavy metal klunk.
Sound025 Hydraulic press/drill.
soun0001 Fatal Fury 1 fall land.
soun0002 Fatal Fury 1 jump.
soun0003 Fatal Fury 1 coin.
soun0004 Fatal Fury 1 hit.
soun0005 BOR bike.
soun0006 Fatal Fury 1 block.
soun0007 SOR “gulp” food pick up.
soun0008 SOR indirect hit.
soun0009 Fatal Fury 1 confirmation.
soun0010 World Heroes jump land.
soun0011 WH choke.
soun0012 Bone snap.
soun0013 SF3 Hard ground impact.
soun0014 World Heroes’ hard ground impact.
soun0015 Real Bout maximum power strike.
soun0016 Real Bout multi pitch power up.
soun0017 KOF “catch”/hand clasp.
Assorted Voices
Voic001 DD2 Burnov laugh.
Voic002 DD2 evil double laugh.
Voic003 Female long alto yell (Kasumi high jump).
Voic004 Female short alto yell (Kasumi short jump).
Voic005 Canine growl 1.
Voic006 Canine growl 2.
Voic007 Canine bark 1.
Voic008 Canine howl 1.
Voic009 Horse neigh1.
Voic010 Horse neigh 2.
Voic011 Canine howl 2.
Voic012 Horse neigh 3.
Voic013 Joe Muashi yell with flame burst sound.
Voic035 KOF Yamazaki Snake Hand
Voic036 DD2 Abore Shoulder attack.
Voic037 DD2 Abobo throw.
Voic038 DD2 Ohara shoulder attack.
Voic039 Unused
Voic040 DD1 Abobo throw.
Voic041 Canine howl 3.
Voic042 Shadow Dancer spell scream.
Voic043 Shadow Dancer Jump yell.
Voic044 Shinobi3 Joe run attack.
Voic045 Shinobi3 Joe death kick.
Voic046 Shadow Dancer fire spell chanting with detonation sound.
Voic047 Shadow Dancer wind spell chanting.
Voic048 Shadow Dancer totem spell chanting.
Voic049 Shadow Dancer spell scream with wind and tornado siren sound.
Voic050 Shadow Dancer Yamoto poof, bark, and charge sound.
Voic051 DD2 Chin jump kick.
Voic052 DD2 Chin attack.
Voic053 SO2 Shiva jump attack/throw.
Voic054 SO2 Ninja throw.
Voic055 Dragon roar (Hanzou Ko ryu ha).
Voic056 DD2 Unknown attack.
Voic057 Female soprano scream.
Voic058 Female pain grunt.
Voic059 Vendetta male burned.
Voic060 Vendetta male burned with incineration sound.
Voic061 Male choke.
Voic062 Alto female scream.
Voic063 SOR2 male death 1.
Voic064 SOR2 male death 2.
Voic065 SOR2 Axel death.
Voic066 SOR2 female death.
Voic067 SOR3 male death 1.
Voic068 SOR3 male boss death.
Voic069 SOR2 Max death.
Voic070 Male scream with cut/bisect sound.
Voic071 SOR2 Skate death.
Voic072 Shadow Dancer boss death.
Voic073 Vendetta male falling sound (“owww!!”).
Voic074 Vendetta boss death.
Voic075 Ninja Spirit PC death.
Voic076 Male scream with dog mauling sound.
Voic077 DD2 Burnov death.
Voic078 DD2 Chin death.
Voic079 Male multi-pitch scream.
Voic095 Male “Tuhh!!”
Voic096 Male “Ha!”
Voic097 Male “Humph!”
Voic098 Male “Uegh!”
Voic099 Male “Ummph!”
Voic100 Male “Yeeah!”
Andy Bogard
vand0001 “Seh!”
vand0002 “Huah!”
vand0003 Light exhale
vand0004 “Gah” pain.
vand0005 “Mrah” pain.
vand0006 “Mowah” pain.
vand0007 “Baby cry” pain.
vand0008 Light attack
vand0009 “Shoken!”
vand0010 “Geki”
vand0011 “Shoooooken!!”
vand0012 “Ku ha dan!”
vand0013 “Shoryudan!”
vand0014 “Zaneiken!”
vand0015 Super finish scream.
vand0016 “Humph”
vand0017 “Yoshi!”
vand0018 Exhale.
vand0019 “Sayaaa!”
vand0020 “Cho reppa dan!”
vand0021 Super finish scream 2.
vand0022 “Shetz!”
vand0023 “Namusan”
vand0024 “Ienoash”
vand0025 Prelude to Tung match.
vand0026 “Tung Sensei!”
vand0027 “Zengu Ikkyu!”
vand0028 “Geese!”
vand0029 “Sono tadoka.”
vand0030 Short growl.
vand0031 “Arryaaah!”
vand0032 “Siiiiiiii!”
vand0033 Wild Ambition overdrive initial attack.
vand0034 “Shrureahh!” ***ERROR** Cannot be exported.
vand0035 “Zanei Reppa!”
vand0036 “Hutz!”
vand0037 “Geki!”
vand0038 “Juhn!”
vand0039 “Metsu!”
vand0040 “Son!”
vand0041 “Baaak!”
vand0042 “Meeetsu!”
vand0043 KO 2 (“uhph.. gomen!”).
vand0044 “Oniotaaaah!”
vand0045 “Shuryahhh!”
vand0034 “Shrureahh!”
sand0001 Hishoken
sand0002 Geki Hishoken
sand0003 Special slash/whoosh
sand0004
sand0005 Fire burst
sand0006 Long whoosh
sand0007 Strong fire burst
sand0008 Cho Reppa Dan burst.
Hanzou (WH)
Voic022 WH Hanzou light attack.
Voic023 WH Hanzou med. attack.
Voic024 WH Hanzou heavy attack.
Voic025 WH Hanzou “Nin!”
Voic026 WH Hanzou “Victory.”
Voic027 WH Hanzou “Double Rekkozan!”
Voic028 WH Hanzou “Koh Ryu Ha!”
Voic029 WH Hanzou “Lariat!”
Voic030 WH Hanzou “Slash of Shimmering!”
Voic031 WH Hanzou ‘Ninpo Korin Kazan!”
Voic032 WH Hanzou “Rekkozan!”
Voic033 WH Hanzou KO.
Voic034 WH Hanzou “Come on!”
Kasumi (DOA)
Voic014 Attack 1.
Voic015 Attack 2.
Voic016 Attack 3 (waterwheel kick).
Voic017 Attack 4.
Voic018 Attack 5 (Hard throw).
Voic019 Attack 6.
Voic020 “I have no time to spare.”
Voic021 “Farewell.”
Muscle Power
Voic080 WH Muscle Power “Number one!”
Voic081 WH Muscle Power quick grunt.
Voic082 WH Muscle Power “Dahhh!!”
Voic083 WH Muscle Power “Gwaoow!”
Voic084 WH Muscle Power “Hungah!”
Voic085 WH Muscle Power pain 1.
Voic086 WH Muscle Power Jab.
Voic087 WH Muscle Power KO.
Voic088 WH Muscle Power “Number onnnnne!”
Voic089 WH Muscle Power “Hoawwww!”
Voic090 WH Muscle Power medium grunt.
Voic091 WH Muscle Power pain 2.
Voic092 Real American sound clip.
Raiden (Big Bear)
vrai0001 “I’ll dance on your grave!”
vrai0002 “Say your prayers wimp!”
vrai0003 “You’re dead meat!”
vrai0004 “Come on!”
vrai0005 “Hum!”
vrai0006 “Hyeee!”
vrai0007 “Hey!”
vrai0008 “Jurreahh!”
vrai0009 “Jeeh!”
vrai0010 “Oooowahhhooo!”
vrai0011 “Nwaaoo!”
vrai0012 “Huaah!”
vrai0013 “Bomba!
vrai0014 “Ready…”
vrai0015 “Go!”
vrai0016 “Dropkick!”
vrai0017 “G…End!”
vrai0018 “I am Raiden!”
vrai0019 “Ichimon!”
vrai0020 “Heh hahahaha!”
vrai0021 “Gwah!”
vrai0022 “Gwhaaaa!”
vrai0023 KO.
Rainbow Mika
vmik0001 “Guh!”
vmik0002 “Uggh!”
vmik0003 “Nuuuh!”
vmik0004 “Euuaggh!”
vmik0005 KO with echo.
vmik0006 KO.
vmik0007 “Kuu!”
vmik0008 “Huh!”
vmik0009 “Uuuah!”
vmik0010 “Bombaaah!”
vmik0011 “Attack!”
vmik0012 “Crush!”
vmik0013 “Victory!”
vmik0014 “Igamaaah!”
vmik0015 “Morataaa!”
vmik0016 “Kimatah!”
vmik0017 “Uryou!’
vmik0018 “Shaah!”
vmik0019 “Oraah!”
vmik0020 “Yah!”
vmik0021 “Kuu!”
vmik0022 “Kona!”
vmik0023 “Rainbow!”
vmik0024 “Ikuze”
vmik0025 Happy bounce.
vmik0026 “Doshta.”
Ray McDougal
vray0001 “DDT!”
vray0002 KO.
vray0003 “Wheel Kick!”
vray0004 “All right!”
Vray0005 “Yeah!”
Retsu
Voic093 SF Retsu Grunt
Voic094 SF Retsu Grunt 2
Ryo Sakazaki
Voic101 Ryo Sakazaki attack 1 (heavy).
Voic102 Ryo Sakazaki “Zanretsuken!”
Voic103 Ryo Sakazaki “Hoah Sho Ko Ken!” (low pitched)
Voic104 Ryo Sakazaki “Raijinsetsu!”
Voic105 Ryo Sakazaki “Hoah Sho Ko Ken!” (normal pitch)
Voic106 Ryo Sakazaki attack 2 (light).
Voic107 Ryo Sakazaki attack 3 (light).
Voic108 Ryo Sakazaki pain 1.
Voic109 Ryo Sakazaki “Ko Oh Ken!”
Voic110 Ryo Sakazaki attack 4 (Kohou).
Voic111 Ryo Sakazaki attack 5 (heavy).
Voic112 Ryo Sakazaki “Ora! ora! ora! ora! ora!”
Voic113 Ryo Sakazaki “Ichi!”
Voic114 Ryo Sakazaki “Hissestu”
Voic115 Ryo Sakazaki attack 5 (heavy).
Voic116 Ryo Sakazaki “Ora! ora!”
Voic117 Ryo Sakazaki “Osu!!”
Voic118 Ryo Sakazaki “Hien Shippu kakyu!”
Voic119 Ryo Sakazaki charging.
Voic120 Ryo Sakazaki KO.
Sho (Breakers Revenge)
vsho0001 Jab.
vsho0002 “Mmpuh!”
vsho0003 “Shwop!”
vsho0004 “Hah!”
vsho0005 “Shoo!”
vsho0006 “Eyahh!”
vsho0007 “Hup!”
vsho0008 “Whuyeah!”
Vsho0009 “YEAAHHH!”
vsho0010 “Kureaaah!”
Terry Bogard
vter0001 FF2 KO.
vter0002 CVS KO.
vter0003 “Uuh!”
vter0004 “Owughh!”
vter0005 “Hey! Come on, come on!”
vter0006 “Geese!”
vter0007 “Hey you!”
vter0008 “Okaaay!”
vter0009 “Burn Knuckle!”
vter0010 “Crack shoot!”
vter0011 “Rising tackle!”
vter0012 “Power wave!”
vter0013 “Power dunk!”
vter0014 “Power…!”
vter0015 “Geyser!”
vter0016 “Are you OK?”
vter0017 “Buster wolf!”
vter0018 “Hah!”
vter0019 “Humph!”
vter0020 “Heyyy!”
vter0021 “Agggh!”
Tung Fu Rue
vtun0001 Roid rage.
Generic Female
voif0001 Long KO scream.
Generic Male
voim0001 High pitched “Ewwluah!” KO.
voim0002 Midtone burst KO.
voim0003 Vendetta on fire scream.
voim0004 SOR3 male KO scream.
voim0005 SOR2 male KO scream 1.
voim0006 SOR2 male KO scream 2 (from sword of Vermillion).
voim0007 Ninja spirit PC death.
voim0008 Long large male KO.
voim0009 Large male “erroooooo!”
voim0010 Quick “Ya!”
voim0011 Punisher male KO 1
voim0012 Punisher male KO 2 (high pitched OW!)
voim0013 Punisher male KO 3 (“yeaahhhh!”)
voim0014 AVP soldier KO 1.
voim0015 AVP soldier KO 2.
voim0016 Fatal Fury 1 “GO!”
voim0017 MK Pain 1.
voim0018 MK male long fall 1.
voim0019 MK male long fall 2.
Generic Voice (robot, alien, etc)
voig0001 High pitch alien screech.
voig0002 Crowd cheer.
voig0003 MK crowd in awe.

Reserved Maps

Use Map
Default 0
Death 1
Burn 2
Shock 3
Freeze 4
Poison 5
Reserved 6 – 10
Color selects 11+

Reserved icons

Icon ID
Default icon0001
Death icon0002
Pain icon0003
Acquire icon0004
Weapon icon0005

Select Screen Shenanigans

Original select screen in Golden Axe Remake was functional, but insufficient for needs of Golden Axe Hackjob. Primary concerns were the following:

  • Main selectable characters each having to accommodate for the others colors within their palettes. This severely limited color choices. For example, Ax could not be given the 2p color palette from Golden Axe The Duel. Doing so required changing his weapon colors, which were shared with Tyris Flare’s clothing.
  • No true carousel effect. While characters were displayed in a ring, they simply swapped location when cycled instead of rotating as in the arcade original.
  • No name display. At no point are characters ever identified. While not a great issue for the the original characters, this is a problem when introducing others.
  • No accommodation for extra characters. It would be rather jarring and unprofessional looking for alternate choices to be inserted into selection cycle when not appearing in “ring” of visible choices.
  • Non selected characters are “burned” when a choice is confirmed. The fire effect used lacks transparency and thematically does not match the various improved effects used throughout the module.

To solve this problem, I decided to use a series of blank silhouettes to serve as the carousel. As characters are cycled, the blank silhouettes rotate in place, then the front silhouette is replaced by current selection. This would allow infinite addition of selectable characters while maintaining the theme.

The original plan was for the silhouettes to be displayed by a separate entity, with rotation direction determined by left/right key press. Unfortunately scripted spawns during the selection screen results in a shutdown. Attempts to make use of the summonentity/spawnentity command also proved unsuccessful; during selection screen these commands are ignored.

This only left having the silhouettes be part of main entity. Main issue was adding a color for the silhouettes to characters with palettes firmly established. I worked around the problem by using the entry 255 in each character’s palette as the silhouette color. In addition to getting around the laborious process of “inserting” a color to existing palettes, it also meant only the palette images needed modification as opposed to every single sprite.

Having the silhouettes displayed within main entity also solved any timing issues, though it did mean rotation would always be in same direction. I was worried unidirectional rotation would look jarring, but in practice it works quite well. The rotation itself is achieved by a “brute force” approach; a series of sprites drawn to give the illusion of a carousel are played as part of the “wait” animation. The memory consumption is minimum however, as the rotating silhouette sprites are reused by every selectable character.

Name display is also part of the main entity, and its color issue was solved in the same manor, except with use of  color entry 254.

The burn effect was the last problem to overcome, but was easily solved. It is achieved by launching a non moving projectile on selection confirmation. While it would have been impossible to use this method for the silhouettes due to timing issues, it works perfectly for burning and allows use of transparency.

As a last touch, the location of each character was widened for more room and player 1 was placed in the center (this module allows 3 players). Player 2 is on the far left, player 3 on the far right. Along with improving aesthetics, by making these changes I have added yet another obstacle to any short sighted attempts at Dreamcast porting.

Bug List

Known Bugs/Issues:

Major

  • Characters cannot use jump attack while holding left/right. 07122010 – Direction key check, and by extension disabling of other keys was for both hold & press. Removed hold check.
  • Ax’s Powerbomb and Gutbuster always KO opponent regardless of remaining HP. 07122020 – Copy and paste errors switched drop and damage properties.
  • Enemy life meter remains on screen after being KO’d by Ax’s Piledriver. 07122010 – Damage and bind release were occurring on same frame.
  • Thieves do not giggle when idle. 07122010 – Old sound command was still in place. Replaced with soun0005() function.
  • Meat carrying thieves are not remapping to green. 07122010 – Alternate palettes were typo’d as “palette”.
  • Bad sound reference: Error, soun0009; Sample: – data/sounds/punch3.wav, Model: amachick. 07152010 – Caused by remnants of older sound scripts. Removed and replaced with current system.
  • Amazon never performs axe throw. 08082010 – Was previously disabled for debugging and never re-enabled.
  • While in throw bind, character can hit enemies as well as allies. This appears to be an engine bug with projectilehit property, but needs thorough review.
  • Wall fail-safe and wall throws triggering when on top of wall/platform. 08072010 – Fixed by adding check for current base of self for wall alt and bound for fail-safe.
  • Alex’s bow shot causing shutdown. Error reports attempting to set animation for effe0002, even though projectile is effe0001. 08072010 – Caused by combination of backward compatibility in projectile function and new select screen burn effect. If model level projectile is defined, script function projectile() ignores its own model parameter and uses model level projectile instead. Fixed by moving burn effect entity from model level to animation level.

Minor

  • Sound system, soun0005() in particular works as designed but is extremely sub optimal with several loops and no sanity clauses.
  • Characters hit by attacks when in throw bind appear very briefly out of place. Not technically a bug. It is due to large offsetting difference caused by small sprites and the need to keep actual locations well above ground while visually near, on, or in it. Resolved.

To Do:

  • Add headband to Ax Battler’s portrait (so color selection is visible during game play screen).
  • Add sound effect when picking up magic jars and food.
  • Auto platform jump working on improper animations.
  • Upgrade grappling scripts to new index based type.
  • Fix alternate attacks against thieves.
  • Fix collision boxes for rise attack.
  • Second attack in attack string not hitting at very close range.
  • Add wall alts for grappling.
  • Upgrade weapon models to new standard.
  • Fix palettes to allow remapping.
  • Replace particle effects for magic.
  • Upgrade sound effects to new random based script type.
  • Add startframe and landframe to RUNATTACK.
  • Add grab alternate to special.
  • Replace flashing based shock and burn animations with remap + particle effect.
  • Add dust effect to jumps and landings.
  • Add jump grappling options.
  • Replace old forward throw with powerbomb.