CastAVote no longer keeps track of the heap. Additionally, VoteEdit should use the facilities provided by VoteModule. For the more paranoid, there is a version of VoteEdit that manages the voted-on heap itself. Pick whichever you trust most. :-)
The eventual aim is to have this module look after all of CastAVote's file functions in ALL CastAVote software. You load this module up when you run the BBS - and any CastAVote utility (except VoteFile) will interface with the module instead of interfacing with the disc drive. As C is faster than BASIC, you will notice the whole system runs faster.
Why 220K? Each string is 61 bytes long (60+zero). There are 15 strings in a vote. There are 220 votes. This is 201300 bytes, and not including results (about 8K more) or internal variables. Say 220K in all. The internal structure is exactly 1K in size, so 220K it is...
Now look at BASIC. BASIC reserves 256 bytes per string... Times 15 times 220. Right. 844800 bytes. Erm... Now each copy of CastAVote loaded would try to grab Nearly 1Mb (+code and internal variables).
The module provides for a mechanism to 'supply' data upon demand, and to reduce CastAVote to a shell that doesn't eat much memory. To add to that, loading two instantations of CastAVote will require much less memory than it used to.
There is a version of VoteModule (1.18c) that builds the vote data on disc (c is for cache). This will then supply data from disc instead of from RAM. It is noticeably slower but uses much less memory. I developed it because 220K was too much when loading C and the BBS software together. It is not an official release.
Later versions of VoteModule are expected to address this problem with the use of linked lists. This will allow VoteModule to claim memory as it needs is, rather than waste 200K on a vote data file that is only ten votes long.
Interrupts - VoteModule does not claim/alter interrupts.
Processor mode - SVC.
Re-entrancy - VoteModule is not re-entrant.
VoteModule_LoadVotes (SWI &16F00)
Load the votes data into memory.
On entry
R0 = 'unsaved data' switch override.
R1 = Pointer to filename.
No filename will default to "<CastAVoteA$Dir>.Data.Data".
On exit
R0 = Operation status:
1 - File loaded okay.
2 - Load failed because of unsaved data.
3 - Load failed because file cannot be found or opened.
4 - Load failed because data is corrupted (when reading string).
5 - Load failed because data is corrupted (when reading word).
Use:
VoteModule_LoadVotes will load the votes data from the file specified,
or the default file. This operation will usually fail if there is
unsaved data. However you can pass a special number in R0 to override
this. The special number is randomly chosen at start-up, and the method
of obtaining this number is currently undocumented. Due to a glitch,
you will never get a code 4 or code 5. It will return with the error
"exit called" or something. I'll get around to this some day. If you
see this, it 99% means your votes data is corrupted - so you'll have
bigger things to worry about than whether or not VoteModule works...
VoteModule will still get upset if no votes file exists, it will moan
about CastAVote not 'seen' by the filer. However it will now load votes
data with 0 votes without crashing.
VoteModule_SaveVotes (SWI &16F01)
Save the votes data to disc.
On entry
R1 = Pointer to filename.
No filename will default to "<CastAVoteA$Dir>.Data.Data".
On exit
R0 = Operation status:
1 - File saved okay.
2 - Save failed. You have to find out why.
Use:
VoteModule_SaveVotes will save the votes data to the specified file, or
default if no name is specified.
In the future, this command may return with '3' in R0 if no filename is
given. To 'ensure' the file, do not use this command, use
VoteModule_Refresh in preference.
VoteModule_Refresh (SWI &16F02)
Save the votes data to disc to the default file ('ensure').
On entry
On exit
R0 = Operation status:
1 - File saved okay.
2 - Save failed. You have to find out why.
Use:
VoteModule_Refresh will save the votes data to the default file. This
call should be used if you want to 'ensure' the file.
Currently, some 'database altering' commands automatically refresh the
database.
However this should not be relied upon, and you should refresh yourself
at selected times in your program.
Refresh will abort with a failure if there is no data loaded.
VoteModule_GetVote (SWI &16F03)
Return a pointer to the requested vote.
On entry
R0 = Required vote.
On exit
R0 = Operation status:
1 - If okay.
2 - If vote is out of range.
R1 = Data:
If R0 = 1 then R1 is a pointer to the data block.
If R0 = 2 then R1 is the number of votes in the database.
Use:
VoteModule_GetVote will return a pointer to a data block (if okay)
which contains the vote you requested.
The data block should be 1024 bytes in length, in the form:
struct votebuffer /* BASIC equivalents:
{ char creator[61]; /* block%+0
char system[61]; /* block%+61
int voted_for; /* block%!124
char question[61]; /* block%+128
char software_opts[61]; /* block%+189
char addinfo_one[61]; /* block%+250
char addinfo_two[61]; /* block%+311
char addinfo_three[61]; /* block%+372
char option_one_text[61]; /* block%+433
int option_one_rslt; /* block%!496
char option_two_text[61]; /* block%+500
int option_two_rslt; /* block%!564
char option_three_text[61]; /* block%+568
int option_three_rslt; /* block%!632
char option_four_text[61]; /* block%+636
int option_four_rslt; /* block%!700
char option_five_text[61]; /* block%+704
int option_five_rslt; /* block%!768
char option_six_text[61]; /* block%+772
int option_six_rslt; /* block%!836
char option_seven_text[61]; /* block%+840
int option_seven_rslt; /* block%!904
char option_eight_text[61]; /* block%+908
int option_eight_rslt; /* block%!972
char reserved_data_block[24]; /* block%+976 *UNUSED*
char vote_is_local[1]; /* block%?1000
char vote_is_hidden[1]; /* block%?1001 *UNUSED*
char vote_is_inaccessible[1]; /* block%?1002 *UNUSED*
char vote_is_locked[1]; /* block%?1003 *UNUSED*
char vote_flags_other[12]; /* block%?1004 *UNUSED*
int vote_data_crc; /* block%!1016
char vote_data_key[1]; /* block%?1020 *UNUSED*
char reserved_developer[1]; /* block%?1021 *UNUSED*
char reserved_user[1]; /* block%?1022 *UNUSED*
char vote_datafile_type[1]; /* block%?1023 *UNUSED*
};
The data block is designed to fit C, not BASIC, thus the odd offsets.
If you use the data block offsets defined, all should work.
The '*UNUSED*' strings are not defined at this time, so you shouldn't
assume anything about their contents.
You must remember that the strings are zero-terminated, and the entire
block could well not contain any newlines.
You should use a function such as the one below to extract the text,
which will extract text terminated with any control character.
DEFFNextracttext(block%)
LOCAL s$
s$=""
WHILE (?block%>31)
s$+=CHR$(?block%) : block%+=1
ENDWHILE
=s$
reserved_data_block
Currently this can contain anything. The contents of which should
not be relied upon.
vote_is_local / vote_is_hidden / vote_is_inaccessible
and vote_is_locked
These flags determine the 'status' of the vote, in the following
form:
Local - Will NOT be exported via NVP.
This is being implemented.
Hidden - Will NOT be shown to voters, creator can see.
Inaccessible - Creator cannot access it either, higher than
hidden.
Locked - User cannot delete their own vote.
The value of these elements should be read with the byte indirection
operator '?'.
However, steps will be taken to ensure the last byte of
vote_flags_other is zero, so you should be able to read the
whole lot as a string, if that would be easier.
The flags are given values corresponding to the Latin1 (ISO 8859/1)
character set, as follows:
'' ( 0) - Old VoteModule in use, doesn't support flags yet.
'X' ( 88) - Flags not yet supported.
'Y' ( 89) - Flag is set.
'N' ( 78) - Flag is unset.
'?' ( 63) - Flag had peculiar/unexpected value.
'¤' (164) - Old style vote, no flags found.
Other values may be introduced in the future. If you receive an
unknown value, you should provide a sensible fallback (usually
assuming the flag IS set if not 'X' or 'Y' or '¤').
vote_flags_other
The contents of these bytes should not be relied upon. However the
protocol defines that the very last byte will always be zero, so you
can, if you wish, treat the flags as a zero-terminated string
instead of reading them individually.
If you do read the flags as a string, you should not assume any
particular string length. The maximum will be sixteen characters,
the minimum could be a zero-length string.
vote_data_crc
In the future, the vote data may be passed through OS_CRC to ensure
that what went in is the same as what is coming out. The CRC will
count from 0 to 1015 (missing the CRC and key words). Upon a failed
CRC, you should retry reading the data and if it fails, reload the
database and retry. If it still fails, print up whatever dire
warning you think appropriate. :-)
Please have the sense to trap '0' CRCs in your code...
Some code will try to apply CRCs, but these values do not
necessarily mean anything and should therefore be ignored.
vote_data_key
In the future, there will be a security option to keep the database
encrypted whilst on disc, though valid information will be passed
via the SWI. This is the 'key' used. The algorithm will remain
undocumented.
reserved_developer / reserved_user
These are reserved for developer/user use, and will be allocated on
a first-come-first-served. You may be allocated a bit inside the
byte, with 15 bits up for grabs. You should not rely on the contents
of these bytes unless you are aware of the significance. When
setting your bits, don't alter any other bits...
bit 0, reserved_developer, means "vote is tagged with VoteEdit".
vote_datafile_type
Informs the user of what file type is in use...
0 - File type unknown or typing not supported.
1 - Pre-2.05 file type (Ancient).
2 - Pre-3 file type (Old).
3 - Version 3 file type (Current).
4 - PC variant of the Version 3 file type.
5 - NatVoter file type (definition deprecated).
6 - Enhanced version 3 file type (spec unfinished).
Note, just because a file type is listed, it does NOT mean that
VoteModule can load it. You should be expecting '0' or '3' or '6'...
This version of VoteModule returns '3', as it can only read type 3
files.
VoteModule_PutVote (SWI &16F04)
Add a vote to the database.
On entry
R0 = Pointer to vote block.
On exit
R0 = Operation status:
1 if added okay.
2 if overflow (more than 220 votes).
10 if creator < 1 character
11 if question < 1 character
12 if option_one_text < 1 character
13 if option_two_text < 1 character
14 if software_opts has no egg ('¤' - denotes flags)
Use:
VoteModule_PutVote will take your pointer and try to update the votes
database by adding the new vote to the END of the current database.
It is assumed by VoteModule that:
* Your data block is in EXACTLY the same format as for
VoteModule_GetVote (above).
* You have checked to ensure the vote is valid (two options etc).
* That the strings are zero-terminated.
If successful, the votes data file will be refreshed - whether you like
it or not. :-)
That is a 'peace of mind' feature.
VoteModule_AddVoteOpt (SWI &16F05)
Update a specified option.
On entry
R0 = Question.
R1 = Option.
R2 = Pointer to user's heap string [not coded yet].
On exit
R0 = Operation status:
1 - If updated okay.
2 - If invalid vote number.
3 - If invalid option number.
R1 = Set to zero [not coded yet].
R2 = Pointer to updated user's heap string [not coded yet].
Use:
VoteModule_AddVoteOpt will update the specified option and totals for
the specified question.
An option is considered invalid if the option text is zero length. A
question is considered invalid if it does not exist. :-)
The username pointer / heap pointer are not currently used.
If successful, the option count and totals will be updated. The database
is not refreshed to disc.
VoteModule_GetVotesCount (SWI &16F06)
Return the number of votes in the database.
On entry
On exit
R0 = Number of questions.
R1 = Total number allowed, will reply '220'.
Use:
This is so you can quickly and easily see how many votes there are in
the database.
VoteModule_Statistics (SWI &16F07)
Return some useful (?) information.
On entry
On exit
R0 = Number of questions.
R1 = Memory reserved for database.
R2 = Pointer to default filename string.
R3 = Unsaved_data.
R4 = Module version, #.xx
R5 = Module version, x.##
R6 = Pointer to start of database memory.
Use:
This replies some useful and not-so-useful information...
Number of questions
The number of active questions.
Memory reserved for database
This is worked out 'by hand' and does not include space reserved for
the unused components. It is a 'rough estimate' value.
Pointer to default filename string.
This is so you can read off the default filename. If you are clever,
you may be able to patch in a different filename for the module to
use. This is NOT recommended. The size allocated is 32 bytes,
which must include a zero-terminator. Repeat, NOT recommended.
:-)
Unsaved_data
'1' if there is unsaved data, or '0' if not. If there is unsaved
data, you should 'refresh' (VoteModule_Refresh).
Module version, #.xx
Replies with the major version number.
Module version, x.##
Replies with the minor version number. Assume the minor number is
two digits long with possible leading zero.
Therefore '2' and '7' would be '2.07' or '1' and '23' would be
'1.23'.
Pointer to start of database memory.
This points to where the database memory starts. If you know how,
you could look directly in the memory for some information. This
VoteModule does not apply any checking on the data, but future
versions may maintain a CRC on the database - so don't even attempt
to 'patch in' your own votes or change anything to do with the votes.
Votes are 1024 bytes long, so the offsets are simple to calculate.
VoteModule_VoteOp (SWI &16F08)
Miscellaneous additional functions that VoteModule provides.
On entry
R0 = Operation:
2 - Delete vote.
3 - Compare vote.
5 - Return heap entry.
6 - Update heap entry.
7 - Delete vote in all heap entries.
8 - Decrement in all heap entries.
9 - Increment in all heap entries.
10 - Tidy all heap entries.
R1++ = Depends on R1.
On exit
Depends on R0.
2 - Delete vote:
Entry R1 = Vote to delete.
Exit R0 = -1 if specified vote more than questions in database.
1 if successful.
2 if cannot open temporary file.
3 if cannot open votes data file.
This will delete the specified vote. It works by dumping all votes,
except the specified, to a file and then reloading that file.
3 - Compare vote:
Entry R1 = String.
Exit R0 = Vote number or '-1'.
Compare first 30 characters of string passed in R1 and return a vote
number, or '-1' if no match. This is intended for NVP to facilitate
duplicate checking. Search is case insensitive.
5 - Return heap entry:
Entry R1 = User name.
Exit R0 = 1 if successful.
2 if "HasVoted" file cannot be opened.
3 if username too long (maximum 35 characters).
4 if username not found. Returns blank heap entry.
Exit R1 = Heap entry (may be "").
This fetches and returns the heap entry for the specified user. This
saves you needing to perform file operations on the "HasVoted" file.
6 - Update heap entry:
Entry R1 = Username.
Entry R2 = Heap entry string.
Exit R0 = 1 if successful.
2 if "XHasVoted" cannot be opened.
3 if (new) "HasVoted" cannot be opened.
5 if username too long (maximum 35 characters).
6 if heap entry too long (maximum 220 characters).
This saves the heap entry for the specified user, again preventing you
from having to access the HasVoted file.
If the operation fails, you must check if a file called
"XHasVoted" exists. If it does, you should rename this as "HasVoted".
What happens: "HasVoted" renamed "XHasVoted", then data copied from
"XHasVoted" to (new) "HasVoted". Updated if necessary or just directly
copied. If no data to be updated, the specified data is appended to the
end of the (new) "HasVoted" file. Then "XHasVoted" is deleted.
7 - Delete vote in all heap entries.
Entry R1 = Vote specifier.
Exit R0 = 1 if successful.
2 if "XHasVoted" cannot be opened.
3 if (new) "HasVoted" cannot be opened.
4 if out of range.
If this replies with 2 or 3, read the note for action 6
(above).
This command deletes the specified vote entry through the entire
voted-on heap.
It does not, however, shift the vote heap subsequent entries down.
The formal protocol is to:
1. Delete the vote.
2. Delete the voted-on entry.
3. Decrement following voted-on entries.
Due to the way operation 3 works, a clever person could miss out step 2
thus saving a little bit of time.
8 - Decrement in all heap entries.
Entry/exit parameters as for action 7 (on previous page).
This does exactly the reverse of action 8 (above).
You should call this after deleting a vote in order to realign the heap.
Failure to do so will mean that voted-on entries for vote #17 (for
example) no longer point to vote#17. This action has not been fully
tested, but has been rewritten for v1.18.
9 - Increment in all heap entries.
Entry/exit parameters as for action 7 (above).
This takes the vote number pointed so in R1 and increments all heap
entries after that vote.
For example:
Vote data = 12345678
You insert a blank vote in position 5 and call this program with
input '5'.
Vote data = 12346789
The use of this command is not recommended as whilst blank votes
can be created, they are technically invalid. This action has not been
fully tested, but has been rewritten for v1.18.
10 - Tidy all heap entries.
Entry/exit parameters as for action 7 (above). R1 ignored.
Tidying consists of:
a. Deleting all blank lines in the file.
b. Rearranging the voted-on entries into numerical order.
Future versions of this may 'loose' any entries referring to higher
numbered votes than currently exist in the votes database.
This function is automatically performed by actions 7, 8 and 9 so you
don't need to explicitly call this after, say, deleting a vote.
VoteModule_AmendVote (SWI &16F09)
Amend an existing vote in the database.
On entry
R0 = Pointer to data block.
R1 = Question.
On exit
R0 = Operation status:
1 if added okay.
2 if overflow (more than 220 votes).
10 if creator < 1 character
11 if question < 1 character
12 if option_one_text < 1 character
13 if option_two_text < 1 character
14 if software_opts has no egg ('¤' - denotes flags)
Use:
VoteModule_AmendVote will take your pointer and try to update the
specified question.
This is very much like VoteModule_PutVote, except you can
specify any question number.
There is a possible problem with the vote being changed whilst it is
loaded in CastAvote, therefore CastAVote will still refuse to operate
when VoteEdit is loaded.
It is assumed by VoteModule that:
* Your data block is in EXACTLY the same format as for
VoteModule_GetVote (above).
* You have checked to ensure the vote is valid (two options etc).
* That the strings are zero-terminated.
The easiest way is to VoteModule_GetVote the data, amend it,
then throw back the same data block.
If successful, the data will be saved to the default file - but you
should not rely upon this, use VoteModule_Refresh...
VoteModule_GetVoteHeader (SWI &16F0A)
Return a pointer to ONLY the vote header.
On entry
R1 = Question.
On exit
R0 = Operation status:
1 - If okay.
2 - If question number is invalid.
R1 = Pointer to data block.
Use:
VoteModule_GetHeader will fill a simple 128 byte data block with only
the header information, in the form:
struct headerblock /* BASIC equivalents:
{ char creator[61]; /* block%+0
char question[61]; /* block%+61
int question_number; /* block%!124
};
This is to allow for 'quick' searching. It is also used by CastAVote in
the "List Votes" option.
*VoteModule_Statistics
Prints some statistics on VoteModule.
Syntax
*VoteModule_Statistics
Parameters
None.
Use
This prints some rudimentary statistics, of which this is an example:
BudgieSoft VoteModule version 1.18 ; to support CastAVote.
VOTES DATABASE LOADED : Yes.
NUMBER OF VOTES IN DATABASE : 140.
FILE FORMAT : Normal.
UNSAVED DATA? : No.
MODULE FULLY OPERATIONAL : Yes.
Coded by : Richard Murray
With the assistance of : Richard Sargeant, Keith Hall,
Rick Decker and Robin Abecasis.
Notable testers and thanks : John Stonier, Steve Pursey, Robin
Abecasis, Keith Hall, Dane Koekoek,
DaviD Dade/Dave Coleman and Chris
Jackson.
Example
*VoteModule_Statistics
Related commands
None
Related SWIs
VoteModule_Statistics
Related vectors
None
*VoteModule_Refresh
Refresh the database.
Syntax
*VoteModule_Refresh
Parameters
None.
Use
This calls VoteModule_Refresh, and prints a simple message to
indicate okay or fail. If you don't want textual replies, use the SWI
"VoteModule_Refresh".
Example
*VoteModule_Refresh
Related commands
None
Related SWIs
VoteModule_Refresh
Related vectors
None
*VoteModule_Extent
Display memory usage information.
Syntax
*VoteModule_Extent
Parameters
None.
Use
This displays some information on the memory use and comparative memory use.
This is the amount of memory required to hold the votes data...
Size of 'votedata' : 225280 bytes
This is the amount of memory required, for the same, in BASIC...
Strings : Take 256 bytes per string, 15 per question.
Integers : Take 4 bytes per integer, 9 per question.
In all : Multiply the above by 220 votes.
Size of 'votedata' in BASIC : 852720 bytes (!)
You should not assume the presence of this command, or anything about
its output. Future versions of VoteModule may not have this command.
Example
*VoteModule_Extent
Related commands
None
Related SWIs
None
Related vectors
None
Firstly, you should define 1024 bytes of memory for the vote to be placed.
Next, SYS "VoteModule_LoadVotes". This call may fail if another CastAVote has been updating the votes, or the last program to use VoteModule failed to refresh.
SYS "VoteModule_GetVotesCount" TO ,questions% should be performed regularly.
To get a vote, simply call SYS "VoteModule_GetVote",vote% TO
reply%,buffer% but be sure you can handle this call failing. If the call
succeeds, then buffer% is filled with 1K of vote data. If the call fails
(vote out of range etc) then buffer% will be returned an integer representing
the current number of available votes.
Attempting to extract questions and options from a failed return will cause
mondo crasho. Totally so gross. Grody to the max. Etc. Etc.
Adding a vote is the reverse of GetVote. You fill in the buffer and send it off to SYS "VoteModule_PutVote", buffer% TO reply% but be ready to catch errors and failed updates. You can work out how to increment an option. It really is dead-easy.
File Formats
DATA
The CastAVote file format is written with PRINT# and read with INPUT#.
The basic file format is:
STRING : {header}
INTEGER: {number of questions}
IF questions>0 REPEAT
{question data (see below)}
UNTIL all_questions_read
STRING: {footer}
BYTES: {extensions}
STRING: {2nd footer}
The header string can be ignored. It is some backwards text to
identify the file (backwards is readable in the file because PRINT#
saves strings backwards).
The questions number is safely anything from 0 to 220. If 0, the
footer will immediately follow, otherwise question data will follow.
question data is detailed below.
The footer will be either "noitaunitnoC" for an extended format
file or "elif fo dnE" for a basic version 3 file. These read as
"Continuation" and "End of file" in Edit...
The extensions are currently undefined. It is likely to be a
block of, say, 128bytes per question. If you cannot handle extensions,
but discover them, you should print up a warning saying "extension data
will be lost if you continue" (unless you have a nifty way to preserve
it).
The 2nd footer will say "elif fo dnE".
As the specification for the extended file format has not been drawn up,
it is unlikely you will find any of the extended data. Of course, you
don't really care as VoteModule will be loading the data for you...
Won't it? :-)
The question data is as follows:
<creator>$ - Who created this question.
1 to 60 characters.
Example: "Hershey's Mr. Goodbar".
<system>$ - system created the vote, BBS name and fidonet address.
1 to 60 characters, fixed format.
Example: "ArcTic BBS (at 2:254/86.0)"
The format is pretty obvious from the above...
Just to clarify for people that don't own NVP - the fidonet address
is the address of the BBS server, not the address of the NVP
tosser.
<voted for>% - Number of people to vote for this.
An integer...
<question>$ - The question.
1 to 60 characters.
Example: "What is the nicest chocolate?"
<software/opt>$ - The software that created this vote and options.
1 to 60 characters, fixed format.
Example: "PizzaFileCreator v1.23 ; ¤0000000000000000"
The "¤" and 16 digits after is necessary. See the flags
detail (below) for more information.
<addinfo line 1>$ - Additional information line 1.
<addinfo line 2>$ - Additional information line 2.
<addinfo line 3>$ - Additional information line 3.
1 to 60 characters. Can be blank.
Example: "Some additional info".
<opt1text>$ - Option 1 text.
<opt1res>% - Option 1 results.
<opt2text>$ - Option 2 text.
<opt2res>% - Option 2 results.
Text: 1 to 60 characters. The first two options must be present.
Text: Example: "Hersheys"
Result: An integer. The collective addition of the results integers
should ideally equal the total. :-)
<opt3text>$ - Option 3 text.
<opt3res>% - Option 3 results.
<opt4text>$ - Option 4 text.
<opt4res>% - Option 4 results.
<opt5text>$ - Option 5 text.
<opt5res>% - Option 5 results.
<opt6text>$ - Option 6 text.
<opt6res>% - Option 6 results.
<opt7text>$ - Option 7 text.
<opt7res>% - Option 7 results.
<opt8text>$ - Option 8 text.
<opt8res>% - Option 8 results.
These options can contain data (text: 1 to 60 characters) or be
blank. If one option is blank, all further options should be blank.
If an option is blank, it must have a "0" result.
Options are 16 bytes (not yet coded):
Bytes 1 and 2:
Bit 0 - Vote is local (exclude from NVP activities).
1 - Vote is hidden from all except SysOp/VoteMaster & creator
2 - Vote is inaccessible to people unless "Registered" flag
set.
4 - Vote is locked (undeletable within CastAVote; use
VoteEdit).
12/13/14/15 - CRC value in hexadecimal.
All other bits/bytes unallocated.
There is a rather gross 'fudge' planned. How can you CRC data that
will be changed upon the value of the CRC? Simple. When the vote is
loaded, the CRC data is extracted and stored in a variable. The four
bytes reserved for CRC are filled with zeros and then the CRC
is checked. The reverse is true for adding the CRC. If you need to
check this yourself (you mean you don't use VoteModule!?) please be
aware of this. Also, please have the sense to skip null CRCs (NVP
will remove any CRC data in the file).
If you use VoteModule, forget these quirks - it'll all be done for
you.