renamed 'src' to 'tools'
This commit is contained in:
1 parent
2305856d63
commit
d5f6f1924e
12 files changed
+6
-6
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -1,262 +0,0 @@
|
||||
/*
|
||||
tcpclient.c
|
||||
Copyright 2019, Paul Gardner-Stephen
|
||||
Copyright 2012, Isaakidis Marios, Daniel Aguado
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
|
||||
//file found = 1
|
||||
//file not found = 0
|
||||
|
||||
//write and overwrite =1
|
||||
//apend =0
|
||||
|
||||
//tcpclient IP port
|
||||
//argc1, argv0: tcpclient.exe
|
||||
//argc2, argv1: IP address TCP Server
|
||||
//argv3, argv2: TCP Server port
|
||||
|
||||
int set_nonblock(int fd)
|
||||
{
|
||||
int retVal=0;
|
||||
|
||||
|
||||
do {
|
||||
if (fd==-1) break;
|
||||
|
||||
int flags;
|
||||
if ((flags = fcntl(fd, F_GETFL, NULL)) == -1)
|
||||
{
|
||||
perror("fcntl");
|
||||
// LOG_ERROR("set_nonblock: fcntl(%d,F_GETFL,NULL)",fd);
|
||||
retVal=-1;
|
||||
break;
|
||||
}
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK | O_NDELAY) == -1)
|
||||
{
|
||||
perror("fcntl");
|
||||
// LOG_ERROR("set_nonblock: fcntl(%d,F_SETFL,n|O_NONBLOCK)",fd);
|
||||
return -1;
|
||||
}
|
||||
} while (0);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
#define MAX_KEYS 1024
|
||||
int key_times[MAX_KEYS];
|
||||
char key_sequences[MAX_KEYS][1024];
|
||||
int key_count=0;
|
||||
|
||||
int main (int argc, char *argv[]) {
|
||||
|
||||
int collect_cycles=5000000;
|
||||
|
||||
// KERNAL part is actually just under 7KB
|
||||
int rom_bottom=0xa000;
|
||||
int rom_top=0xffff;
|
||||
char *filename=NULL;
|
||||
|
||||
int opt;
|
||||
|
||||
while ((opt = getopt(argc, argv, "f:b:t:c:k:v:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'b':
|
||||
rom_bottom=strtoll(optarg,NULL,16);
|
||||
break;
|
||||
case 't':
|
||||
rom_top=strtoll(optarg,NULL,16);
|
||||
break;
|
||||
case 'f':
|
||||
filename=optarg;
|
||||
break;
|
||||
case 'c':
|
||||
collect_cycles=atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
system(optarg);
|
||||
sleep(3);
|
||||
break;
|
||||
case 'k':
|
||||
// Scheduled key events
|
||||
if (key_count>=MAX_KEYS) {
|
||||
fprintf(stderr,"Too many -k arguments. Increase MAX_KEYS?\n");
|
||||
exit(-1);
|
||||
}
|
||||
if (sscanf(optarg,"%d:%[^~]",&key_times[key_count],key_sequences[key_count])<2) {
|
||||
fprintf(stderr,"Malformed argument to -k. Must be <cycle number>:<VICE compatible key sequence>\n"
|
||||
" RETURN should be written \\x0d\n"
|
||||
"\n");
|
||||
exit(-1);
|
||||
}
|
||||
key_count++;
|
||||
break;
|
||||
default: /* '?' */
|
||||
fprintf(stderr, "Usage: %s [-v <vice command line>] [-b <ROM bottom hex>] [-t <ROM top hex>] [-f <file to load and run>] [-c <cycles to collect data>] [[-k <time>:<key sequence> ] ...]\n",
|
||||
argv[0]);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
int sockfd, rc, serverPort, nBytes;
|
||||
struct sockaddr_in localAddr, servAddr;
|
||||
struct hostent *h;
|
||||
|
||||
/* Check parameters from command line */
|
||||
|
||||
serverPort = 6510;
|
||||
if(serverPort<=0 || serverPort>65535)//check number of TCP server port
|
||||
{
|
||||
fprintf(stderr, "The port number given is wrong.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
h = gethostbyname("localhost");
|
||||
if(h==NULL)//check assigment of TCP server host
|
||||
{
|
||||
perror("Unknown host ");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Create TCP socket */
|
||||
|
||||
servAddr.sin_family = h->h_addrtype;
|
||||
memcpy((char *) &servAddr.sin_addr.s_addr, h->h_addr_list[0], h->h_length);
|
||||
servAddr.sin_port = htons(serverPort);
|
||||
|
||||
sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if(sockfd < 0)//check TCP socket is created correctly
|
||||
{
|
||||
perror("Cannot open socket ");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Bind any port number */
|
||||
|
||||
localAddr.sin_family = AF_INET;
|
||||
localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
localAddr.sin_port = htons(0);
|
||||
|
||||
rc = bind(sockfd, (struct sockaddr *) &localAddr, sizeof(localAddr));
|
||||
if(rc < 0)//check TCP socket is bind correctly
|
||||
{
|
||||
perror("Cannot bind on TCP port ");
|
||||
close(sockfd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Connect to TCP server */
|
||||
|
||||
rc = connect(sockfd, (struct sockaddr *) &servAddr, sizeof(servAddr));
|
||||
if(rc < 0)//check TCP socket is connected correctly
|
||||
{
|
||||
perror("Cannot connect ");
|
||||
close(sockfd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Starting TCP client...\n");
|
||||
|
||||
set_nonblock(sockfd);
|
||||
|
||||
write(sockfd,"reset 0\r",strlen("reset 0\n"));
|
||||
|
||||
int current_key=0;
|
||||
|
||||
int last_pc=0, last_sp=0xff;
|
||||
|
||||
int max_cycles=0;
|
||||
|
||||
unsigned char buffer[65536];
|
||||
while(1) {
|
||||
nBytes = read(sockfd, buffer, sizeof(buffer));
|
||||
if (nBytes >= 1) {
|
||||
write(sockfd,"step\rstep\rstep\rstep\r",5*1);
|
||||
// fprintf(stdout,"%d: %s\n",nBytes,buffer);
|
||||
int pc,sp,cycles;
|
||||
int nf=sscanf((char *)buffer,".C:%x%*[^:]:%*x X:%*x Y:%*x SP:%x %*[^ ] %d",&pc,&sp,&cycles);
|
||||
if (nf==3) {
|
||||
// fprintf(stdout,"%x %x %d\n",pc,sp,cycles);
|
||||
// Detect entry into ROM via code
|
||||
if (pc<0xc000||pc>=0xe000)
|
||||
if (pc>=rom_bottom&&pc<=rom_top) {
|
||||
if (last_pc<rom_bottom||last_pc>rom_top) {
|
||||
// PC has just entered the ROM
|
||||
fprintf(stdout,"$%04x called from $%04x @ cycle %d\n",pc,last_pc,cycles);
|
||||
}
|
||||
}
|
||||
// Detect entry into ROM via interrupt
|
||||
if (pc>=rom_bottom&&pc<=rom_top)
|
||||
if (sp==(last_sp-3)) {
|
||||
// PC has just entered the ROM
|
||||
fprintf(stdout,"$%04x called from via interrupt @ cycle %d\n",pc,cycles);
|
||||
}
|
||||
last_pc=pc; last_sp=sp;
|
||||
}
|
||||
|
||||
// LOAD only after BASIC has started
|
||||
if (last_pc==0xe5cd&&filename) {
|
||||
/*
|
||||
To load and a program in VICE via the monitor interface:
|
||||
bload "filename" 0 07ff
|
||||
> 07ff 0 0
|
||||
keybuf run\x0d
|
||||
|
||||
*/
|
||||
char cmd[1024];
|
||||
snprintf(cmd,1024,"bload \"%s\" 0 07ff\r",filename);
|
||||
write(sockfd,cmd,strlen(cmd));
|
||||
usleep(1000000);
|
||||
write(sockfd,"> 07ff 0 0\r",11);
|
||||
usleep(100000);
|
||||
write(sockfd,"keybuf run\\x0d\r",15);
|
||||
|
||||
filename=NULL;
|
||||
}
|
||||
|
||||
if (current_key<key_count&&cycles>key_times[current_key]) {
|
||||
char cmd[1024];
|
||||
snprintf(cmd,1024,"keybuf %s\r",key_sequences[current_key]);
|
||||
write(sockfd,cmd,strlen(cmd));
|
||||
fprintf(stderr,">>> Keyboard injection: %s\n\r",cmd);
|
||||
current_key++;
|
||||
}
|
||||
|
||||
if (cycles>(max_cycles+10000)) {
|
||||
max_cycles=cycles;
|
||||
// fprintf(stderr,"%d cycles\n",cycles);
|
||||
}
|
||||
|
||||
// Terminate emulator when done
|
||||
if (cycles>=collect_cycles) {
|
||||
write(sockfd,"\rquit\r",6);
|
||||
usleep(1000000);
|
||||
break;
|
||||
}
|
||||
|
||||
} else usleep(1);
|
||||
}
|
||||
close(sockfd);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
//
|
||||
// Common parts of utilities written in C++
|
||||
// for providing uniform user experience
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
|
||||
#if defined(WIN32) || defined(_WIN32)
|
||||
#define DIR_SEPARATOR "\\"
|
||||
#else
|
||||
#define DIR_SEPARATOR "/"
|
||||
#endif
|
||||
|
||||
|
||||
const std::string BANNER_LINE = "//-------------------------------------------------------------------------------------------";
|
||||
|
||||
|
||||
void ERROR()
|
||||
{
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
void ERROR(const std::string &message)
|
||||
{
|
||||
std::cout << "\n" << "ERROR: " << message << "\n\n";
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
void printBannerLineTop()
|
||||
{
|
||||
std::cout << "\n\n\n" << BANNER_LINE << "\n";
|
||||
}
|
||||
|
||||
void printBannerLineBottom()
|
||||
{
|
||||
std::cout << BANNER_LINE << "\n\n";
|
||||
}
|
||||
|
||||
|
||||
class DualStream
|
||||
{
|
||||
public:
|
||||
DualStream(std::ostream& str1, std::ostream& str2) : str1(str1), str2(str2) {}
|
||||
|
||||
template<class T> DualStream &operator<<(const T& x)
|
||||
{
|
||||
str1 << x;
|
||||
str2 << x;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream& str1;
|
||||
std::ostream& str2;
|
||||
};
|
||||
@@ -1,492 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <strings.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <math.h>
|
||||
|
||||
// https://www.c64-wiki.com/wiki/BASIC_token
|
||||
/*
|
||||
The URLs here provide examples of other BASIC dialects that include the specified commands
|
||||
or keywords.
|
||||
All except VERIFY are known with certainty to be part of other BASIC dialects, and thus
|
||||
cannot be subject to copyright infringement when implemented on a C64.
|
||||
|
||||
The VERIFY Command however appears in other programming languages, and as a single word,
|
||||
is the obvious complement to LOAD and SAVE when considering the following storage functions:
|
||||
|
||||
1. Load a file from storage into memory.
|
||||
2. Save a file from memory to storage.
|
||||
3. Verify that a file has been correctly written from memory to storage.
|
||||
|
||||
Note that verify is the key verb in the behaviour of this function. Therefore it is the
|
||||
obvious choice for the name for such a function, taking into account the convention of
|
||||
LOAD and SAVE. Indeed, that this is so is highlighted by the fact that it was used in the
|
||||
C64 ROM. Therefore there is no exercise of creativity here, and a reasonable person would
|
||||
likely select this name independently. Also, single words cannot be copyright.
|
||||
|
||||
Further, the list is a list of facts, and thus not copyrightable.
|
||||
|
||||
We sourced the list from a public source, https://www.c64-wiki.com/wiki/BASIC, which has
|
||||
been public on the internet for a long time, as have many other similar sources.
|
||||
That website offers the page under the GFDL license, and we can therefore take the list
|
||||
under those license terms if we wish, should the need arise.
|
||||
|
||||
Note that in providing these justifications above, we do not in any way acknowledge that
|
||||
without these conditions that the BASIC keyword list could be copyright, but provide this
|
||||
information as a further layer of defence against any claims that it could somehow
|
||||
infringe on the copyrights of the C64/C65/C128 etc ROMs.
|
||||
|
||||
*/
|
||||
char *keyword_list[]={
|
||||
"END", // https://www.landsnail.com/a2ref.htm
|
||||
"FOR", // https://www.landsnail.com/a2ref.htm
|
||||
"NEXT", // https://www.landsnail.com/a2ref.htm
|
||||
"DATA", // https://www.landsnail.com/a2ref.htm
|
||||
"INPUT#", // https://www.atariarchives.org/creativeatari/Using_Disks_With_Atari_Basic.php
|
||||
"INPUT", // https://www.landsnail.com/a2ref.htm
|
||||
"DIM", // https://www.landsnail.com/a2ref.htm
|
||||
"READ", // https://www.landsnail.com/a2ref.htm
|
||||
"LET", // https://en.wikipedia.org/wiki/Atari_BASIC
|
||||
"GOTO", // https://www.landsnail.com/a2ref.htm
|
||||
"RUN", // https://www.landsnail.com/a2ref.htm
|
||||
"IF", // https://www.landsnail.com/a2ref.htm
|
||||
"RESTORE", // https://www.landsnail.com/a2ref.htm
|
||||
"GOSUB", // https://www.landsnail.com/a2ref.htm
|
||||
"RETURN", // https://www.landsnail.com/a2ref.htm
|
||||
"REM", // https://www.landsnail.com/a2ref.htm
|
||||
"STOP", // https://www.landsnail.com/a2ref.htm
|
||||
"ON", // https://www.landsnail.com/a2ref.htm
|
||||
"WAIT", // http://www.picaxe.com/BASIC-Commands/Time-Delays/wait/
|
||||
"LOAD", // https://www.landsnail.com/a2ref.htm
|
||||
"SAVE", // https://www.landsnail.com/a2ref.htm
|
||||
"VERIFY", // https://golang.org/cmd/go/
|
||||
"DEF", // https://www.landsnail.com/a2ref.htm
|
||||
"POKE", // https://www.landsnail.com/a2ref.htm
|
||||
"PRINT#", // https://www.atariarchives.org/creativeatari/Using_Disks_With_Atari_Basic.php
|
||||
"PRINT", // https://www.landsnail.com/a2ref.htm
|
||||
"CONT", // https://www.landsnail.com/a2ref.htm
|
||||
"LIST", // https://www.landsnail.com/a2ref.htm
|
||||
"CLR", // Apple I Replica Creation: Back to the Garage, p125
|
||||
"CMD", // https://en.wikipedia.org/wiki/List_of_DOS_commands
|
||||
"SYS", // https://www.lifewire.com/dos-commands-4070427
|
||||
"OPEN", // https://www.atariarchives.org/creativeatari/Using_Disks_With_Atari_Basic.php
|
||||
"CLOSE", // https://www.atariarchives.org/creativeatari/Using_Disks_With_Atari_Basic.php
|
||||
"GET", // https://www.landsnail.com/a2ref.htm
|
||||
"NEW", // https://www.landsnail.com/a2ref.htm
|
||||
"TAB(", // http://www.antonis.de/qbebooks/gwbasman/tab.html
|
||||
"TO", // https://www.landsnail.com/a2ref.htm
|
||||
"FN", // https://www.landsnail.com/a2ref.htm
|
||||
"SPC(", // http://www.antonis.de/qbebooks/gwbasman/spc.html
|
||||
"THEN", // https://www.landsnail.com/a2ref.htm
|
||||
"NOT", // https://www.landsnail.com/a2ref.htm
|
||||
"STEP", // https://www.landsnail.com/a2ref.htm
|
||||
"+", // https://www.landsnail.com/a2ref.htm
|
||||
"-", // https://www.landsnail.com/a2ref.htm
|
||||
"*", // https://www.landsnail.com/a2ref.htm
|
||||
"/", // https://www.landsnail.com/a2ref.htm
|
||||
"^", // https://www.landsnail.com/a2ref.htm
|
||||
"AND", // https://www.landsnail.com/a2ref.htm
|
||||
"OR", // https://www.landsnail.com/a2ref.htm
|
||||
">", // https://www.landsnail.com/a2ref.htm
|
||||
"=", // https://www.landsnail.com/a2ref.htm
|
||||
"<", // https://www.landsnail.com/a2ref.htm
|
||||
"SGN", // https://www.landsnail.com/a2ref.htm
|
||||
"INT", // https://www.landsnail.com/a2ref.htm
|
||||
"ABS", // https://www.landsnail.com/a2ref.htm
|
||||
"USR", // https://www.landsnail.com/a2ref.htm
|
||||
"FRE", // http://www.antonis.de/qbebooks/gwbasman/fre.html
|
||||
"POS", // http://www.antonis.de/qbebooks/gwbasman/pos.html
|
||||
"SQR", // https://www.landsnail.com/a2ref.htm
|
||||
"RND", // https://www.landsnail.com/a2ref.htm
|
||||
"LOG", // https://www.landsnail.com/a2ref.htm
|
||||
"EXP", // https://www.landsnail.com/a2ref.htm
|
||||
"COS", // https://www.landsnail.com/a2ref.htm
|
||||
"SIN", // https://www.landsnail.com/a2ref.htm
|
||||
"TAN", // https://www.landsnail.com/a2ref.htm
|
||||
"ATN", // https://www.landsnail.com/a2ref.htm
|
||||
"PEEK", // https://www.landsnail.com/a2ref.htm
|
||||
"LEN", // https://www.landsnail.com/a2ref.htm
|
||||
"STR$", // https://www.landsnail.com/a2ref.htm
|
||||
"VAL", // https://www.landsnail.com/a2ref.htm
|
||||
"ASC", // https://www.landsnail.com/a2ref.htm
|
||||
"CHR$", // https://www.landsnail.com/a2ref.htm
|
||||
"LEFT$", // https://www.landsnail.com/a2ref.htm
|
||||
"RIGHT$", // https://www.landsnail.com/a2ref.htm
|
||||
"MID$", // https://www.landsnail.com/a2ref.htm
|
||||
"GO", // https://en.wikipedia.org/wiki/Goto
|
||||
NULL
|
||||
};
|
||||
|
||||
/*
|
||||
These error messages are generally so well known, that it seems silly to have to even point out that they are
|
||||
so widely used and known, if not completely genericised.
|
||||
The most of them are inherited from the MICROSOFT BASIC on which the C64's BASIC is based, so for those, there
|
||||
cannot even be any claim to copyright over them in any exclusive way.
|
||||
|
||||
However, copyright of such short phrases that express ideas in short form cannot be copyrighted:
|
||||
https://fairuse.stanford.edu/2003/09/09/copyright_protection_for_short/
|
||||
*/
|
||||
char *error_list[]={
|
||||
"TOO MANY FILES", // https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/error-messages/too-many-files
|
||||
"FILE OPEN", // https://forums.autodesk.com/t5/3ds-max-forum/quot-file-open-error-quot/td-p/4036150
|
||||
"FILE NOT OPEN", // https://www.synthetos.com/topics/file-not-open-error/
|
||||
"FILE NOT FOUND", // https://answers.microsoft.com/en-us/windows/forum/windows_10-files/file-not-found-check-the-file-name-and-try-again/0734e837-b9f7-485e-93c5-5592f804b4c8
|
||||
"DEVICE NOT PRESENT", // https://answers.microsoft.com/en-us/windows/forum/all/device-not-present/d9f96498-cf92-4ba0-9076-a206cf058131
|
||||
"NOT INPUT FILE", // https://stackoverflow.com/questions/13055889/sed-with-literal-string-not-input-file/37682812 http://iama.stupid.cow.org/OldComputerMagazines/Commodore%20Power-Play%20100%25/Commodore_Power-Play_1985_Issue_16_V4_N04_Aug_Sep.pdf
|
||||
"NOT OUTPUT FILE", // http://www.un4seen.com/forum/?topic=12547.0
|
||||
"MISSING FILENAME", // https://arduino.stackexchange.com/questions/22839/missing-filename-compilation-error
|
||||
"ILLEGAL DEVICE NUMBER", // https://comp.unix.solaris.narkive.com/dkqcQFg3/svm-mirror-not-working-illegal-major-device-number
|
||||
"NEXT WITHOUT FOR", // https://stackoverflow.com/questions/18232928/compile-error-next-without-for-vba
|
||||
"SYNTAX", // https://en.wikipedia.org/wiki/Syntax_error
|
||||
"RETURN WITHOUT GOSUB", // https://stackoverflow.com/questions/11467746/return-without-gosub-when-using-subforms-in-access
|
||||
"OUT OF DATA", // http://forum.qbasicnews.com/index.php?topic=8619.0
|
||||
"ILLEGAL QUANTITY", // https://community.dynamics.com/ax/f/33/t/238113
|
||||
"OVERFLOW", // https://www.webopedia.com/TERM/O/overflow_error.html
|
||||
"OUT OF MEMORY", // https://support.microsoft.com/en-au/help/126962/out-of-memory-error-message-appears-when-you-have-a-large-number-of-pr
|
||||
"UNDEF\'D STATEMENT", // http://ivanx.com/appleii/magicgoto/
|
||||
"BAD SUBSCRIPT", // https://www.computing.net/answers/windows-8/how-does-bad-subscript-error-appear-in-gwbasic/2301.html
|
||||
"REDIM\'D ARRAY", // https://fjkraan.home.xs4all.nl/comp/apple2faq/app2asoftfaq.html
|
||||
"DIVISION BY ZERO", // https://fjkraan.home.xs4all.nl/comp/apple2faq/app2asoftfaq.html
|
||||
"ILLEGAL DIRECT", // https://hwiegman.home.xs4all.nl/gw-man/Appendix%20A.html
|
||||
"TYPE MISMATCH", // https://fjkraan.home.xs4all.nl/comp/apple2faq/app2asoftfaq.html
|
||||
"STRING TOO LONG", // https://fjkraan.home.xs4all.nl/comp/apple2faq/app2asoftfaq.html
|
||||
"FILE DATA", // https://www.minitool.com/data-recovery/data-error-crc.html
|
||||
"FORMULA TOO COMPLEX", // https://fjkraan.home.xs4all.nl/comp/apple2faq/app2asoftfaq.html
|
||||
"CAN\'T CONTINUE", // https://hwiegman.home.xs4all.nl/gw-man/Appendix%20A.html
|
||||
"UNDEF\'D FUNCTION", // https://www.saatchiart.com/print/Painting-Undef-d-Function-3495/16167/3829193/view
|
||||
"VERIFY", // https://forums.openvpn.net/viewtopic.php?t=22040
|
||||
"LOAD", // https://www.discogs.com/Gimmik-Load-Error/release/482462
|
||||
// Non-error messages
|
||||
"READY.\r", // #29 // https://www.ibm.com/support/knowledgecenter/zosbasics/com.ibm.zos.zconcepts/zconc_whatistsonative.htm https://github.com/stefanhaustein/expressionparser
|
||||
"BREAK", // #30
|
||||
" IN ", // #31
|
||||
"BRK AT $", // #32
|
||||
"ERROR", // #33 // Simply the word error that is attached to the other parts of messages https://fjkraan.home.xs4all.nl/comp/apple2faq/app2asoftfaq.html
|
||||
" BASIC BYTES FREE", // #34 // https://github.com/stefanhaustein/expressionparser
|
||||
"MEMORY CORRUPT", // #35
|
||||
"NOT IMPLEMENTED", // #36
|
||||
NULL};
|
||||
|
||||
// XXX: create separate list for Kernal
|
||||
// "LOADING", // #30 // https://community.tibco.com/wiki/how-show-loading-prompt-when-reloading-external-data
|
||||
// "VERIFYING", // https://discussions.apple.com/thread/8087203
|
||||
// "SAVING",
|
||||
|
||||
// Token $FF is end of message, so we can have only 255 unique words
|
||||
#define MAX_WORDS 255
|
||||
char *words[MAX_WORDS];
|
||||
int word_count=0;
|
||||
|
||||
#define MAX_LEN 1024
|
||||
unsigned char message_tokens[MAX_LEN];
|
||||
int token_count=0;
|
||||
|
||||
unsigned char packed_words[MAX_LEN];
|
||||
int packed_len=0;
|
||||
|
||||
unsigned char packed_keywords[MAX_LEN];
|
||||
int packedkey_len=0;
|
||||
|
||||
struct char_freq {
|
||||
unsigned char c;
|
||||
int count;
|
||||
};
|
||||
|
||||
int total_chars=0;
|
||||
int repeats=0;
|
||||
|
||||
int cmpfreq(const void *a,const void *b)
|
||||
{
|
||||
const struct char_freq *aa=a,*bb=b;
|
||||
if (aa->count<bb->count) return 1;
|
||||
if (aa->count>bb->count) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MAX_CHARS 256
|
||||
struct char_freq char_freqs[MAX_CHARS];
|
||||
int char_max=0;
|
||||
|
||||
int find_word(char *w)
|
||||
{
|
||||
int i;
|
||||
for(i=0;i<word_count;i++) {
|
||||
if (!strcmp(w,words[i])) {
|
||||
// fprintf(stderr,"Word '%s' is repeated.\n",w);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
words[word_count++]=strdup(w);
|
||||
return word_count-1;
|
||||
}
|
||||
|
||||
void process_word(char *w)
|
||||
{
|
||||
int num=find_word(w);
|
||||
message_tokens[token_count++]=num;
|
||||
return;
|
||||
}
|
||||
|
||||
void end_of_message(void)
|
||||
{
|
||||
message_tokens[token_count++]=0xFF;
|
||||
}
|
||||
|
||||
int not_vowel(char c)
|
||||
{
|
||||
switch(c) {
|
||||
case 'A': case 'E': case 'I': case 'O': case 'U':
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int calc_stats(const char *s)
|
||||
{
|
||||
for(int j=0;s[j];j++)
|
||||
if (s[j]!=' ') {
|
||||
int char_num=0;
|
||||
for(char_num=0;char_num<char_max;char_num++)
|
||||
if (s[j]==char_freqs[char_num].c) break;
|
||||
if (s[j]&0x80) fprintf(stderr,"Word '%s' contains funny char 0x%02x\n",s,s[j]);
|
||||
if (char_num>MAX_CHARS) {
|
||||
fprintf(stderr,"Too many unique characters in message list. Max is %d\n",MAX_CHARS);
|
||||
exit(-1);
|
||||
}
|
||||
if (char_num>=char_max) char_max=char_num+1;
|
||||
char_freqs[char_num].c=s[j];
|
||||
char_freqs[char_num].count++;
|
||||
total_chars++;
|
||||
if (j&&s[j]==s[j-1]) repeats++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pack_word(const char *w,unsigned char *out,int *len)
|
||||
{
|
||||
|
||||
fprintf(stderr,"Packing word '%s' at offset 0x%03x\n",w,*len);
|
||||
int nybl_waiting=0;
|
||||
int nybl_val=0;
|
||||
for(int j=0;w[j];j++) {
|
||||
int char_num;
|
||||
for(char_num=0;char_num<char_max;char_num++)
|
||||
if (w[j]==char_freqs[char_num].c) break;
|
||||
fprintf(stderr,"'%c' = char #%d ",w[j],char_num);
|
||||
if (char_num<14) {
|
||||
if (nybl_waiting) {
|
||||
// We have a nybl already waiting, so join them up
|
||||
int byte=(nybl_val<<4)+(char_num+1);
|
||||
out[(*len)++]=byte;
|
||||
nybl_waiting=0;
|
||||
fprintf(stderr," M[$%02x]",byte);
|
||||
} else {
|
||||
// Queue this nybl to pack in a byte
|
||||
nybl_waiting=1;
|
||||
nybl_val=char_num+1;
|
||||
fprintf(stderr," Q[$%x]",char_num+1);
|
||||
}
|
||||
} else {
|
||||
// It's a less frequent letter that we have to encode
|
||||
// using a whole byte, and flush out any waiting nybl with a
|
||||
// $F token to indicate long code follows.
|
||||
if (nybl_waiting) {
|
||||
int byte=(nybl_val<<4)+0xF;
|
||||
out[(*len)++]=byte;
|
||||
nybl_waiting=0;
|
||||
fprintf(stderr," F[$%02x]",byte);
|
||||
if (char_num&0x80) {
|
||||
fprintf(stderr,"ERROR: Extended characters must be < 0x80\n");
|
||||
exit(-1);
|
||||
}
|
||||
out[(*len)++]=w[j];
|
||||
fprintf(stderr," XS[$%02x]",w[j]);
|
||||
} else {
|
||||
if (char_num < (14+13)) {
|
||||
out[(*len)++]=0xF1+(char_num-14);
|
||||
fprintf(stderr," L[$%02x]",0xF1+(char_num-14));
|
||||
} else {
|
||||
if (w[j+1]) {
|
||||
out[(*len)++]=0xFF;
|
||||
out[(*len)++]=w[j];
|
||||
fprintf(stderr," X[$FF,$%02x]",char_num);
|
||||
}
|
||||
else {
|
||||
out[(*len)++]=0xFE;
|
||||
out[(*len)++]=w[j];
|
||||
fprintf(stderr," X0[$FE,$%02x]\n",char_num);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fprintf(stderr,"\n");
|
||||
}
|
||||
|
||||
// Flush any pending nybl out, or if none, write $00
|
||||
if (nybl_waiting) {
|
||||
int byte=(nybl_val<<4)+0x0;
|
||||
out[(*len)++]=byte;
|
||||
nybl_waiting=0;
|
||||
fprintf(stderr," EF[$%02x]\n",byte);
|
||||
} else {
|
||||
int byte=0x00;
|
||||
out[(*len)++]=byte;
|
||||
nybl_waiting=0;
|
||||
fprintf(stderr," E[$%02x]\n",byte);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int raw_size=0;
|
||||
int keyraw_size=0;
|
||||
|
||||
// Get letter frequencies
|
||||
for(int i=0;i<26;i++) char_freqs[i].count=0;
|
||||
for(int i=0;error_list[i];i++) calc_stats(error_list[i]);
|
||||
for(int i=0;keyword_list[i];i++) calc_stats(keyword_list[i]);
|
||||
|
||||
qsort(char_freqs,char_max,sizeof(struct char_freq),cmpfreq);
|
||||
fprintf(stderr,"Letter frequencies of %d chars (%d unique, %d repeats):\n",total_chars,char_max,repeats);
|
||||
for(int i=0;i<char_max;i++) fprintf(stderr,"%02x '%c' : %d (%.2f bits)\n",
|
||||
char_freqs[i].c,
|
||||
char_freqs[i].c,
|
||||
char_freqs[i].count,log(total_chars*1.0/char_freqs[i].count)/log(2));
|
||||
|
||||
// Get 14 most frequent out to use as 4-bit tokens 1 - 14.
|
||||
// Token 0 = end of word.
|
||||
// Token 15 indicates opposite nybl identifies which of the other 15 possible values is used
|
||||
// for less-frequent letters. (Low nybl $0 is reserved to make it easy to find end of each
|
||||
// compressed word, at the cost of one possible symbol).
|
||||
// Thus common letters take 4 bits, and uncommon ones take 8, i.e., they can never be longer,
|
||||
// but can be upto 1/2 the length.
|
||||
|
||||
|
||||
for(int i=0;error_list[i];i++) raw_size+=strlen(error_list[i]);
|
||||
for(int i=0;keyword_list[i];i++) keyraw_size+=strlen(keyword_list[i]);
|
||||
|
||||
for(int i=0;error_list[i];i++)
|
||||
{
|
||||
char word[1024];
|
||||
int offset=0;
|
||||
for(int j=0;error_list[i][j];j++) {
|
||||
if (error_list[i][j]==' ') {
|
||||
// Found word boundary
|
||||
int w=0;
|
||||
for(int k=offset;k<j;k++) word[w++]=error_list[i][k];
|
||||
word[w++]=0;
|
||||
offset=j+1;
|
||||
// fprintf(stderr,"Found word '%s'\n",word);
|
||||
process_word(word);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Get last word on line
|
||||
int w=0;
|
||||
for(int k=offset;k<error_list[i][k];k++) word[w++]=error_list[i][k];
|
||||
word[w++]=0;
|
||||
// fprintf(stderr,"Found word '%s'\n",word);
|
||||
process_word(word);
|
||||
end_of_message();
|
||||
}
|
||||
}
|
||||
|
||||
// Now encode the words
|
||||
for(int i=0;i<word_count;i++) {
|
||||
fprintf(stderr,"$%03X : ",i);
|
||||
int old_offset=packed_len;
|
||||
pack_word(words[i],packed_words,&packed_len);
|
||||
if ((packed_len-old_offset)>=strlen(words[i])) {
|
||||
if ((packed_len-old_offset)>strlen(words[i]))
|
||||
fprintf(stderr," Packed word '%s' longer than original: ",words[i]);
|
||||
else
|
||||
fprintf(stderr," Packed word '%s' no smaller than original: ",words[i]);
|
||||
for(int j=old_offset;j<packed_len;j++)
|
||||
fprintf(stderr," $%02X",packed_words[j]);
|
||||
fprintf(stderr,"\n");
|
||||
}
|
||||
}
|
||||
for(int i=0;keyword_list[i];i++) {
|
||||
fprintf(stderr,"$%03X : ",i);
|
||||
int old_offset=packedkey_len;
|
||||
pack_word(keyword_list[i],packed_keywords,&packedkey_len);
|
||||
if ((packedkey_len-old_offset)>=strlen(keyword_list[i])) {
|
||||
if ((packedkey_len-old_offset)>strlen(keyword_list[i]))
|
||||
fprintf(stderr," Packed word '%s' longer than original: ",keyword_list[i]);
|
||||
else
|
||||
fprintf(stderr," Packed word '%s' no smaller than original: ",keyword_list[i]);
|
||||
for(int j=old_offset;j<packedkey_len;j++)
|
||||
fprintf(stderr," $%02X",packed_keywords[j]);
|
||||
fprintf(stderr,"\n");
|
||||
}
|
||||
}
|
||||
|
||||
int word_bytes=0;
|
||||
for(int i=0;i<word_count;i++) word_bytes+=strlen(words[i]);
|
||||
|
||||
if (packed_len>255) {
|
||||
fprintf(stderr,"ERROR: packed_len>255\n");
|
||||
exit(-1);
|
||||
}
|
||||
if (token_count>255) {
|
||||
fprintf(stderr,"ERROR: token_count>255\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
printf("packed_message_chars:\n");
|
||||
for(int i=0;i<char_max;i++) {
|
||||
printf("\t.byte $%02X // '%c' ",char_freqs[i].c,char_freqs[i].c != 0xD ? char_freqs[i].c : 127);
|
||||
if (i<14) printf(" = $%x (nybl)\n",i+1);
|
||||
else if (i<(14+13)) printf(" = $F%x / $xF $%02X\n",i-14+1,char_freqs[i].c);
|
||||
else printf(" = $FE/F + $%02X\n",char_freqs[i].c);
|
||||
}
|
||||
|
||||
printf("packed_message_tokens:\n");
|
||||
for(int i=0;i<token_count;i++)
|
||||
{
|
||||
if ((token_count-i)>=8) {
|
||||
printf("\t.byte $%02x,$%02x,$%02x,$%02x,$%02x,$%02x,$%02x,$%02x\n",
|
||||
message_tokens[i+0],message_tokens[i+1],message_tokens[i+2],message_tokens[i+3],
|
||||
message_tokens[i+4],message_tokens[i+5],message_tokens[i+6],message_tokens[i+7]);
|
||||
i+=7;
|
||||
} else
|
||||
printf("\t.byte $%02x\n",message_tokens[i]);
|
||||
}
|
||||
|
||||
printf("packed_message_words:\n");
|
||||
for(int i=0;i<packed_len;i++)
|
||||
{
|
||||
if ((packed_len-i)>=8) {
|
||||
printf("\t.byte $%02x,$%02x,$%02x,$%02x,$%02x,$%02x,$%02x,$%02x\n",
|
||||
packed_words[i+0],packed_words[i+1],packed_words[i+2],packed_words[i+3],
|
||||
packed_words[i+4],packed_words[i+5],packed_words[i+6],packed_words[i+7]);
|
||||
i+=7;
|
||||
} else
|
||||
printf("\t.byte $%02x\n",packed_words[i]);
|
||||
}
|
||||
|
||||
printf("packed_keywords:\n");
|
||||
for(int i=0;i<packedkey_len;i++)
|
||||
{
|
||||
if ((packedkey_len-i)>=8) {
|
||||
printf("\t.byte $%02x,$%02x,$%02x,$%02x,$%02x,$%02x,$%02x,$%02x\n",
|
||||
packed_keywords[i+0],packed_keywords[i+1],packed_keywords[i+2],packed_keywords[i+3],
|
||||
packed_keywords[i+4],packed_keywords[i+5],packed_keywords[i+6],packed_keywords[i+7]);
|
||||
i+=7;
|
||||
} else
|
||||
printf("\t.byte $%02x\n",packed_keywords[i]);
|
||||
}
|
||||
printf("\t.label packed_keyword_table_len = $%X\n",packedkey_len);
|
||||
|
||||
fprintf(stderr,"Error list takes %d bytes raw.\n",raw_size);
|
||||
fprintf(stderr,"%d token bytes used to encode all messages.\n",token_count);
|
||||
fprintf(stderr,"Plus %d bytes for the words\n",packed_len);
|
||||
fprintf(stderr,"Space saving = %d bytes\n",raw_size-packed_len-token_count);
|
||||
|
||||
fprintf(stderr,"\nKeyword list takes %d bytes raw.\n",keyraw_size);
|
||||
fprintf(stderr,"Keyword list takes %d bytes packed.\n",packedkey_len);
|
||||
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
//
|
||||
// Utility to generate various floating points constants
|
||||
// in a Commodore-specific variable format
|
||||
//
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <vector>
|
||||
|
||||
//
|
||||
// Basic types definition
|
||||
//
|
||||
|
||||
typedef struct ConstEntry
|
||||
{
|
||||
ConstEntry(const std::string &constName, double constValue) :
|
||||
constName(constName),
|
||||
constValue(constValue),
|
||||
outString(".error \"not generated\"")
|
||||
{
|
||||
};
|
||||
|
||||
const std::string constName;
|
||||
const double constValue;
|
||||
|
||||
std::string outString;
|
||||
|
||||
} ConstEntry;
|
||||
|
||||
//
|
||||
// Constants to output
|
||||
//
|
||||
|
||||
std::vector<ConstEntry> GLOBAL_constants =
|
||||
{
|
||||
// Constants from:
|
||||
// - https://www.c64-wiki.com/wiki/BASIC-ROM
|
||||
// Computes Mapping the Commodore 64, pages 103, 105, 113, 114, 116
|
||||
|
||||
ConstEntry( "HALF", 0.5 ),
|
||||
ConstEntry( "NEG_HALF", -0.5 ),
|
||||
ConstEntry( "ONE", 1.0 ),
|
||||
ConstEntry( "TEN", 10.0 ),
|
||||
ConstEntry( "NEG_32768", -32768.0 ),
|
||||
|
||||
ConstEntry( "PI", M_PI ),
|
||||
ConstEntry( "SQR_2", std::sqrt(2.0) ),
|
||||
ConstEntry( "INV_SQR_2", 1.0 / std::sqrt(2.0) ),
|
||||
ConstEntry( "LOG_2", std::log(2.0) ),
|
||||
ConstEntry( "INV_LOG_2", 1.0 / std::log(2.0) ),
|
||||
};
|
||||
|
||||
//
|
||||
// Command line settings
|
||||
//
|
||||
|
||||
std::string CMD_outFile = "out.s";
|
||||
|
||||
//
|
||||
// Common helper functions
|
||||
//
|
||||
|
||||
void printUsage()
|
||||
{
|
||||
std::cout << "\n" <<
|
||||
"usage: generate_constants [-o <out file>]" << "\n\n";
|
||||
}
|
||||
|
||||
void printBanner()
|
||||
{
|
||||
printBannerLineTop();
|
||||
std::cout << "// Generating floating point constants" << "\n";
|
||||
printBannerLineBottom();
|
||||
}
|
||||
|
||||
//
|
||||
// Top-level functions
|
||||
//
|
||||
|
||||
void parseCommandLine(int argc, char **argv)
|
||||
{
|
||||
int opt;
|
||||
|
||||
// Retrieve command line options
|
||||
|
||||
while ((opt = getopt(argc, argv, "o:")) != -1)
|
||||
{
|
||||
switch(opt)
|
||||
{
|
||||
case 'o': CMD_outFile = optarg; break;
|
||||
default: printUsage(); ERROR();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Constants generation
|
||||
//
|
||||
|
||||
std::string toAssemblerString(const std::string &constName, double constValue)
|
||||
{
|
||||
uint8_t outFloat[5] = { 0 };
|
||||
|
||||
// Retrieve mantissa nad exponent
|
||||
|
||||
int exponent = -0x80;
|
||||
double mantissa = frexp(std::abs(constValue), &exponent);
|
||||
|
||||
// Round the mantissa to output format precission
|
||||
|
||||
const double coeff = 256.0 * 256.0 * 256.0 * 256.0;
|
||||
|
||||
double intPart;
|
||||
if (modf(mantissa * coeff, &intPart) >= 0.5)
|
||||
{
|
||||
mantissa = (intPart + 1.0) / coeff;
|
||||
}
|
||||
else
|
||||
{
|
||||
mantissa = intPart / coeff;
|
||||
}
|
||||
|
||||
if (mantissa >= 1.0)
|
||||
{
|
||||
mantissa = 0.5;
|
||||
exponent++;
|
||||
}
|
||||
|
||||
// Add a bias to exponent
|
||||
|
||||
exponent += 0x80;
|
||||
|
||||
// Check if output format can contain such a number
|
||||
|
||||
if (exponent > 0xFF)
|
||||
{
|
||||
ERROR(std::string("const '") + constName + "' abs value too large");
|
||||
}
|
||||
else if (exponent <= 0)
|
||||
{
|
||||
ERROR(std::string("const '") + constName + "' abs value too small");
|
||||
}
|
||||
|
||||
// Set output constant exponent
|
||||
|
||||
outFloat[0] = exponent;
|
||||
|
||||
// Set output constant mantissa
|
||||
|
||||
for (uint8_t idxByte = 1; idxByte <= 4; idxByte++)
|
||||
{
|
||||
for (uint8_t idxBit = 0; idxBit <= 7; idxBit++)
|
||||
{
|
||||
outFloat[idxByte] = outFloat[idxByte] << 1;
|
||||
if (mantissa >= 0.5)
|
||||
{
|
||||
outFloat[idxByte]++;
|
||||
mantissa -= 0.5;
|
||||
}
|
||||
|
||||
mantissa *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (mantissa > 0.0) ERROR(std::string("const '") + constName + "' export error");
|
||||
if (outFloat[1] < 0x80) ERROR(std::string("const '") + constName + "' not normalized");
|
||||
|
||||
// Set output constant sign
|
||||
|
||||
if (constValue >= 0.0)
|
||||
{
|
||||
outFloat[1] = outFloat[1] & 0x7F;
|
||||
}
|
||||
|
||||
// Return the output constant as string for assembler
|
||||
|
||||
char buf[256] = { 0 };
|
||||
snprintf(buf, sizeof(buf), "$%02X, $%02X, $%02X, $%02X, $%02X // %22.10f",
|
||||
outFloat[0],
|
||||
outFloat[1],
|
||||
outFloat[2],
|
||||
outFloat[3],
|
||||
outFloat[4],
|
||||
constValue);
|
||||
|
||||
std::string outDef = std::string("\n.macro put_const_") + constName + "()\n{\n\t.byte " + buf + "\n}\n";
|
||||
|
||||
return outDef;
|
||||
}
|
||||
|
||||
|
||||
void writeConstants()
|
||||
{
|
||||
// Convert constants to assembler strings
|
||||
|
||||
for (auto &entry : GLOBAL_constants)
|
||||
{
|
||||
entry.outString = toAssemblerString(entry.constName, entry.constValue);
|
||||
}
|
||||
|
||||
// Remove old file
|
||||
|
||||
unlink(CMD_outFile.c_str());
|
||||
|
||||
// Open output file for writing
|
||||
|
||||
std::ofstream outFile(CMD_outFile, std::fstream::out | std::fstream::trunc);
|
||||
if (!outFile.good()) ERROR(std::string("can't open oputput file '") + CMD_outFile + "'");
|
||||
|
||||
// Write header
|
||||
|
||||
outFile << "//\n// Generated file - do not edit\n//\n\n";
|
||||
|
||||
// Write constants
|
||||
|
||||
for (auto &entry : GLOBAL_constants)
|
||||
{
|
||||
outFile << entry.outString;
|
||||
}
|
||||
|
||||
if (!outFile.good())
|
||||
{
|
||||
outFile.close();
|
||||
unlink(CMD_outFile.c_str());
|
||||
ERROR(std::string("can't write oputput file '") + CMD_outFile + "'");
|
||||
}
|
||||
|
||||
// Close the file
|
||||
|
||||
outFile.close();
|
||||
|
||||
std::cout << std::string("Constants written to: ") + CMD_outFile + "\n\n";
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Main function
|
||||
//
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
parseCommandLine(argc, argv);
|
||||
|
||||
printBanner();
|
||||
writeConstants();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
unsigned char frame0000[]={// border,bg,chars,colors
|
||||
11,6,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,227,227,227,227,227,227,223,32,160,160,223,32,160,160,223,32,160,160,223,32,160,160,223,32,32,32,32,13,32,5,32,7,32,1,32,32,32,32,32,
|
||||
32,227,227,227,227,227,227,227,223,95,205,205,223,95,223,32,32,95,223,32,32,95,223,95,223,32,32,2,32,1,32,19,32,9,32,3,32,32,32,32,
|
||||
32,227,227,227,227,227,227,227,227,223,95,223,95,223,95,205,32,32,95,205,95,223,95,205,95,223,32,32,45,45,45,45,45,45,45,32,32,32,32,32,
|
||||
32,227,227,227,227,227,227,227,227,227,223,95,223,95,32,95,160,160,32,95,160,160,32,95,223,95,32,32,32,32,49,46,48,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,18,5,1,4,25,46,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,160,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,15,1,1,1,15,1,1,1,15,1,1,1,15,1,1,1,14,1,1,1,1,1,1,1,1,1,1,14,14,14,14,14,
|
||||
14,13,13,13,13,13,13,13,13,13,13,13,13,13,13,1,1,13,13,1,1,13,13,13,13,1,1,13,13,13,13,13,13,13,13,13,14,14,14,14,
|
||||
14,7,7,7,7,7,7,7,7,7,15,15,15,15,15,15,1,1,15,15,15,15,15,15,15,15,14,1,14,14,14,14,14,14,14,1,1,14,14,14,
|
||||
14,10,10,10,10,10,10,10,10,10,10,14,14,14,15,14,14,14,15,14,14,14,15,14,14,14,15,14,1,1,15,15,15,1,1,1,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,1,1,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,1,1,14,14,14,14,
|
||||
14,1,1,1,1,1,1,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,1,14,14,14,14,
|
||||
14,1,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,1,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
|
||||
14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14
|
||||
};
|
||||
// META: 40 25 C64 upper
|
||||
@@ -1,61 +0,0 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include "basic005.c"
|
||||
|
||||
unsigned char bytes[65536];
|
||||
int len=0;
|
||||
|
||||
unsigned char colour_codes[16]={
|
||||
144,5,28,159,156,30,31,158,
|
||||
129,149,150,151,152,153,154,155
|
||||
};
|
||||
|
||||
int main(int argc,char **argv)
|
||||
{
|
||||
int reverse=0;
|
||||
int last_colour=-1;
|
||||
for(int y=0;y<24;y++) {
|
||||
unsigned char *line=&frame0000[43+y*40];
|
||||
unsigned char *colour=&frame0000[43+1000+y*40];
|
||||
int w=39;
|
||||
while(w&&line[w-1]==' ') w--;
|
||||
for(int x=0;x<w;x++) {
|
||||
// Update colour if required
|
||||
if (colour[x]!=last_colour) {
|
||||
bytes[len++]=colour_codes[colour[x]];
|
||||
last_colour=colour[x];
|
||||
}
|
||||
if ((line[x]&0x80)^reverse) {
|
||||
// Toggle reverse video flag
|
||||
if (reverse) bytes[len++]=0x92; else bytes[len++]=0x12;
|
||||
reverse^=0x80;
|
||||
}
|
||||
// Write char
|
||||
if (line[x]<0x20) bytes[len++]=line[x]+0x40;
|
||||
else if (line[x]>=0x40&&line[x]<0x60) bytes[len++]=line[x]+0x20;
|
||||
else if (line[x]>=0xe0)
|
||||
bytes[len++]=(line[x]&0x7f)+0x40;
|
||||
else if (line[x]>=0x80)
|
||||
bytes[len++]=line[x];
|
||||
else
|
||||
bytes[len++]=line[x];
|
||||
}
|
||||
|
||||
// Add end of line marker if required
|
||||
if (w<40) bytes[len++]=13;
|
||||
}
|
||||
|
||||
// Skip blank lines at the end
|
||||
while(bytes[len-1]==0x0d) len--;
|
||||
|
||||
bytes[len++]=0;
|
||||
|
||||
printf("; Used %d bytes\n",len);
|
||||
printf("startup_banner:");
|
||||
for(int i=0;i<len;i++) {
|
||||
if (!(i&0xf)) printf("\n .byte ");
|
||||
printf("$%02X",bytes[i]);
|
||||
if ((i&0xf)!=0xf) printf(",");
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
/*
|
||||
Pre-process the various assembly source files to create a single
|
||||
file for assembly, that has everything where it needs to live.
|
||||
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DO_ERROR(X) { retVal=-1; fprintf(stderr,"%s:%d:%s():%s\n",__FILE__,__LINE__,__func__,X); break; }
|
||||
|
||||
#define MAX_FILES (128*1024)
|
||||
typedef struct source_file {
|
||||
char *file;
|
||||
char *label;
|
||||
int address;
|
||||
|
||||
// For getting routine sizes from first run of KickAss
|
||||
int low_addr;
|
||||
int high_addr;
|
||||
int size;
|
||||
|
||||
// For knowing when we have output the various routines
|
||||
int written;
|
||||
} source_file;
|
||||
|
||||
source_file source_files[MAX_FILES];
|
||||
int source_count=0;
|
||||
|
||||
static int compare_source(const void* a, const void* b)
|
||||
{
|
||||
source_file* aa = (source_file*)a;
|
||||
source_file* bb = (source_file*)b;
|
||||
|
||||
if (aa->address<bb->address) return -1;
|
||||
if (aa->address>bb->address) return 1;
|
||||
return strcmp(aa->file,bb->file);
|
||||
}
|
||||
|
||||
int dump_file(FILE *out,const char *name)
|
||||
{
|
||||
int retVal=0;
|
||||
FILE *f=NULL;
|
||||
|
||||
do {
|
||||
f=fopen(name,"r");
|
||||
if (!f) DO_ERROR("Failed to read file for input");
|
||||
char line[8192];
|
||||
while(!feof(f)) {
|
||||
line[0]=0; fgets(line,8192,f);
|
||||
fprintf(out,"%s",line);
|
||||
}
|
||||
} while(0);
|
||||
|
||||
if (f) fclose(f);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
void usage(void)
|
||||
{
|
||||
fprintf(stderr,"usage: preprocess [-d <directory>] [-l <start/low address>] [-h <end/high address>]\n");
|
||||
|
||||
}
|
||||
|
||||
int main(int argc,char **argv)
|
||||
{
|
||||
|
||||
int retVal=0;
|
||||
DIR *d=NULL;
|
||||
char *directory=".";
|
||||
struct dirent *de=NULL;
|
||||
|
||||
int start_address=0xe4d3;
|
||||
int end_address=0xffff+1;
|
||||
|
||||
do {
|
||||
|
||||
int opt;
|
||||
|
||||
while ((opt=getopt(argc,argv,"d:l:h:")) != -1) {
|
||||
switch(opt) {
|
||||
case 'd': directory=optarg; break;
|
||||
case 'l': start_address=strtol(optarg,NULL,16); break;
|
||||
case 'h': end_address=strtol(optarg,NULL,16)+1; break;
|
||||
default:
|
||||
usage();
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
// Get list of files
|
||||
d=opendir(directory);
|
||||
if (!d) DO_ERROR("opendir() failed");
|
||||
while((de=readdir(d))!=NULL) {
|
||||
if (strlen(de->d_name)>2) {
|
||||
if (de->d_name[0]!='.'&&de->d_name[0]!='#') // ignore temp files etc
|
||||
if (!strcmp(&de->d_name[strlen(de->d_name)-2],".s")) {
|
||||
if (strcmp(de->d_name,"combined.s")) {
|
||||
if (source_count>=MAX_FILES) DO_ERROR("Too many source files. Increase MAX_FILES?");
|
||||
source_files[source_count].file=strdup(de->d_name);
|
||||
source_files[source_count].label=strdup(de->d_name);
|
||||
for (char *charptr = source_files[source_count].label; *charptr != 0; charptr++) {
|
||||
if (*charptr == '.') *charptr = '_'; // KickAss does not like full stops in labels
|
||||
}
|
||||
source_files[source_count].address=-1;
|
||||
source_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (retVal) break;
|
||||
closedir(d); d=NULL;
|
||||
|
||||
fprintf(stderr,"Found %d source files\n",source_count);
|
||||
|
||||
// Now get routine addresses of everything
|
||||
for(int i=0;i<source_count;i++) {
|
||||
u_int32_t addr;
|
||||
char funcname[1024];
|
||||
if (sscanf(source_files[i].file,"%x.%[^.]",&addr,funcname)==2) {
|
||||
// Function must appear at specific address
|
||||
source_files[i].address=addr;
|
||||
// printf("function '%s' should be placed at $%04X\n",funcname,addr);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr,"Got fixed routine addresses\n");
|
||||
|
||||
// Sort files into order.
|
||||
// The sorted order is the order the files will appear in the combined source file.
|
||||
// This means that address prefixes on names will be in the right order, but function
|
||||
// names will not.
|
||||
qsort(source_files,source_count,sizeof(source_file),compare_source);
|
||||
|
||||
fprintf(stderr,"Sorted.\n");
|
||||
|
||||
// for(int i=0;i<source_count;i++) {
|
||||
// fprintf(stderr,"$%04X : %s\n",
|
||||
// source_files[i].address!=0x7ffffff?source_files[i].address:0,
|
||||
// source_files[i].file);
|
||||
// }
|
||||
|
||||
// First pass is to work out the size of each routine, so that we can pack them optimally
|
||||
// around the routines that are located at fixed addresses.
|
||||
unlink("temp.sym");
|
||||
unlink("temp.s");
|
||||
FILE *out=fopen("temp.s","w");
|
||||
if (!out) DO_ERROR("Could not write to temp.s");
|
||||
// Start at $100, so that no local data gets accesses using ZP addressing modes during this pass,
|
||||
// which would otherwise upset things later
|
||||
fprintf(out,"\t* = $100\n");
|
||||
for(int i=0;i<source_count;i++) {
|
||||
fprintf(out,"// Source file '%s'\n",source_files[i].file);
|
||||
if (source_files[i].file[0]!=',') fprintf(out,"__routine_start_%s:\n",source_files[i].label);
|
||||
char path[8192];
|
||||
snprintf(path,8192,"%s/%s",directory,source_files[i].file);
|
||||
if (dump_file(out,path)) DO_ERROR("Could not read source file");
|
||||
if (source_files[i].file[0]!=',') fprintf(out,"__routine_end_%s:\n",source_files[i].label);
|
||||
}
|
||||
fclose(out);
|
||||
char cmd[8192];
|
||||
snprintf(cmd,8192,"java -jar assembler/KickAss.jar temp.s -symbolfile -o /dev/null");
|
||||
fprintf(stderr,"Running first pass to get size of routines...\n");
|
||||
system(cmd);
|
||||
|
||||
// Parse temp.sym contents to get size of every routine
|
||||
fprintf(stderr,"Reading sizes of routines...\n");
|
||||
out=fopen("temp.sym","r");
|
||||
if (!out) DO_ERROR("Could not open temp.sym for reading. Did KickAss fail due to an error in one of the assembly files?");
|
||||
while(!feof(out)) {
|
||||
int addr;
|
||||
char label[8192];
|
||||
char line[8192];
|
||||
line[0]=0; fgets(line,8192,out);
|
||||
if (sscanf(line,".label __routine_start_%[^=]=$%x",label,&addr)==2) {
|
||||
for(int i=0;i<source_count;i++)
|
||||
if (!strcmp(label,source_files[i].label))
|
||||
{ source_files[i].low_addr=addr;
|
||||
source_files[i].size=source_files[i].high_addr-source_files[i].low_addr;
|
||||
break; }
|
||||
}
|
||||
if (sscanf(line,".label __routine_end_%[^=]=$%x",label,&addr)==2) {
|
||||
for(int i=0;i<source_count;i++)
|
||||
if (!strcmp(label,source_files[i].label))
|
||||
{ source_files[i].high_addr=addr;
|
||||
source_files[i].size=source_files[i].high_addr-source_files[i].low_addr;
|
||||
break; }
|
||||
}
|
||||
}
|
||||
fclose(out);
|
||||
|
||||
fprintf(stderr,"Got routine sizes:\n");
|
||||
for(int i=0;i<source_count;i++)
|
||||
fprintf(stderr,"%s : %d bytes\n",source_files[i].file,source_files[i].size);
|
||||
|
||||
// Now write the routines out in a deterministic order that respects the requirement
|
||||
// of some routines to be at fixed addresses.
|
||||
|
||||
// Start at beginning of actual kernal data
|
||||
int address=start_address;
|
||||
|
||||
char filename[8192];
|
||||
snprintf(filename,8192,"%s/combined.s",directory);
|
||||
unlink(filename);
|
||||
out=fopen(filename,"w");
|
||||
if (!out) DO_ERROR("Could not write to combined.s");
|
||||
|
||||
fprintf(out,"\t.segment Main [start=$%04x, min=$%04x, max=$%04x, outBin=\"OUT.BIN\", fill]\n" ,
|
||||
start_address, start_address, end_address - 1);
|
||||
fprintf(out,"\t* = $%04x\n",address);
|
||||
|
||||
// First write files of length 0, so that all the zeropage location definitions are
|
||||
// known to KickAss - otherwise it will assume 16-bit address is needed, which will backfire
|
||||
int written_count = 0;
|
||||
for(int i=0;i<source_count;i++) {
|
||||
if (source_files[i].size == 0) {
|
||||
fprintf(out,"\n// Source file %s\n",source_files[i].file);
|
||||
char filename[8192];
|
||||
snprintf(filename,8192,"%s/%s",directory,source_files[i].file);
|
||||
if (dump_file(out,filename)) DO_ERROR("Could not dump 0-size routine");
|
||||
source_files[i].written=1;
|
||||
}
|
||||
else source_files[i].written=0;
|
||||
}
|
||||
// XXX - We should use dynamic programming optimisation to pack the routines
|
||||
// optimally in the space, so that when we get near to full, we don't run out
|
||||
// of space due to fragmentation.
|
||||
for(;written_count<source_count;written_count++) {
|
||||
// First, find the next allocated address
|
||||
int next_allocated_address=0xffff;
|
||||
int next_fixed_routine=-1;
|
||||
for(int i=0;i<source_count;i++)
|
||||
if (!source_files[i].written) {
|
||||
if (source_files[i].address>=address)
|
||||
if (source_files[i].address<next_allocated_address) {
|
||||
next_allocated_address=source_files[i].address;
|
||||
next_fixed_routine=i;
|
||||
}
|
||||
}
|
||||
int space=next_allocated_address-address;
|
||||
|
||||
int biggest=-1;
|
||||
int biggest_size=-1;
|
||||
for(int i=0;i<source_count;i++)
|
||||
if (!source_files[i].written) {
|
||||
if (source_files[i].address==-1) {
|
||||
if (source_files[i].size>biggest_size) {
|
||||
if (source_files[i].size<=space) {
|
||||
biggest_size=source_files[i].size;
|
||||
biggest=i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if done
|
||||
if (next_fixed_routine==-1&&biggest==-1) break;
|
||||
|
||||
fprintf(stderr,"At address $%04x, next allocated address is $%04X. Current space is %d bytes\n",
|
||||
address,next_allocated_address,space);
|
||||
if (biggest>-1)
|
||||
fprintf(stderr,"Biggest floating routine that fits is '%s' (%d bytes)\n",
|
||||
source_files[biggest].file,biggest_size);
|
||||
if (biggest==-1) {
|
||||
// Nothing else fits here. So write next_fixed_routine
|
||||
// fprintf(stderr,"Writing file %s\n",source_files[next_fixed_routine].file);
|
||||
fprintf(out,"\n// Source file %s @ $%04X\n",source_files[next_fixed_routine].file,address);
|
||||
fprintf(out,"\t* = $%04x\n", next_allocated_address);
|
||||
char filename[8192];
|
||||
snprintf(filename,8192,"%s/%s",directory,source_files[next_fixed_routine].file);
|
||||
if (dump_file(out,filename)) DO_ERROR("Could not dump fixed address routine");
|
||||
source_files[next_fixed_routine].written=1;
|
||||
address=next_allocated_address+source_files[next_fixed_routine].size;
|
||||
} else {
|
||||
fprintf(out,"\n// Source file %s @ $%04x\n",source_files[biggest].file,address);
|
||||
// fprintf(stderr,"Writing file %s\n",source_files[biggest].file);
|
||||
fprintf(out,"\t* = $%04x\n", address);
|
||||
|
||||
char filename[8192];
|
||||
snprintf(filename,8192,"%s/%s",directory,source_files[biggest].file);
|
||||
if (dump_file(out,filename)) DO_ERROR("Could not dump floating routine");
|
||||
source_files[biggest].written=1;
|
||||
address+=source_files[biggest].size;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(out);
|
||||
|
||||
if (retVal) break;
|
||||
|
||||
for(int i=0;i<source_count;i++)
|
||||
if (!source_files[i].written) {
|
||||
fprintf(stderr,"ERROR: Did not write source file '%s'\n",source_files[i].file);
|
||||
DO_ERROR("Did not write all source files. Did they not fit?");
|
||||
}
|
||||
if (retVal) break;
|
||||
|
||||
fprintf(stderr,"Assembling combined source...\n");
|
||||
snprintf(cmd,8192,"java -jar assembler/KickAss.jar %s/combined.s -symbolfile -vicesymbols -binfile -o %s/OUT.BIN",directory,directory);
|
||||
system(cmd);
|
||||
|
||||
} while(0);
|
||||
|
||||
if (d) closedir(d);
|
||||
return retVal;
|
||||
}
|
||||
@@ -1,720 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2010 Guillaume Cottenceau.
|
||||
* Copyright 2015-2018 Paul Gardner-Stephen.
|
||||
*
|
||||
* This software may be freely redistributed under the terms
|
||||
* of the X11 license.
|
||||
*
|
||||
*/
|
||||
|
||||
/* ============================================================= */
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#define PNG_DEBUG 3
|
||||
#include <png.h>
|
||||
|
||||
/* ============================================================= */
|
||||
|
||||
char *vhdl_prefix=
|
||||
"library IEEE;\n"
|
||||
"use IEEE.STD_LOGIC_1164.ALL;\n"
|
||||
"use ieee.numeric_std.all;\n"
|
||||
"use work.debugtools.all;\n"
|
||||
"\n"
|
||||
"--\n"
|
||||
"entity charrom is\n"
|
||||
"port (Clk : in std_logic;\n"
|
||||
" address : in integer range 0 to 4095;\n"
|
||||
" -- chip select, active low \n"
|
||||
" cs : in std_logic;\n"
|
||||
" data_o : out std_logic_vector(7 downto 0);\n"
|
||||
"\n"
|
||||
" writeclk : in std_logic;\n"
|
||||
" -- Yes, we do have a write enable, because we allow modification of ROMs\n"
|
||||
" -- in the running machine, unless purposely disabled. This gives us\n"
|
||||
" -- something like the WOM that the Amiga had.\n"
|
||||
" writecs : in std_logic;\n"
|
||||
" we : in std_logic;\n"
|
||||
" writeaddress : in unsigned(11 downto 0);\n"
|
||||
" data_i : in std_logic_vector(7 downto 0)\n"
|
||||
" );\n"
|
||||
"end charrom;\n"
|
||||
"\n"
|
||||
"architecture Behavioral of charrom is\n"
|
||||
"\n"
|
||||
"-- 4K x 8bit pre-initialised RAM for character ROM\n"
|
||||
"\n"
|
||||
"type ram_t is array (0 to 4095) of std_logic_vector(7 downto 0);\n"
|
||||
"signal ram : ram_t := (\n"
|
||||
"\n";
|
||||
|
||||
char *vhdl_suffix=
|
||||
");\n"
|
||||
"\n"
|
||||
"begin\n"
|
||||
"\n"
|
||||
"--process for read and write operation.\n"
|
||||
"PROCESS(Clk,ram,writeclk)\n"
|
||||
"BEGIN\n"
|
||||
"\n"
|
||||
" if(rising_edge(writeClk)) then \n"
|
||||
" if writecs='1' then\n"
|
||||
" if(we='1') then\n"
|
||||
" ram(to_integer(writeaddress)) <= data_i;\n"
|
||||
" end if;\n"
|
||||
" end if;\n"
|
||||
" data_o <= ram(address);\n"
|
||||
" end if;\n"
|
||||
"END PROCESS;\n"
|
||||
"\n"
|
||||
"end Behavioral;\n";
|
||||
|
||||
/* ============================================================= */
|
||||
|
||||
int x, y;
|
||||
|
||||
int width, height;
|
||||
png_byte color_type;
|
||||
png_byte bit_depth;
|
||||
|
||||
png_structp png_ptr;
|
||||
png_infop info_ptr;
|
||||
int number_of_passes;
|
||||
png_bytep * row_pointers;
|
||||
|
||||
FILE *infile;
|
||||
FILE *outfile;
|
||||
|
||||
/* ============================================================= */
|
||||
|
||||
void abort_(const char * s, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, s);
|
||||
vfprintf(stderr, s, args);
|
||||
fprintf(stderr, "\n");
|
||||
va_end(args);
|
||||
abort();
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
|
||||
void read_png_file(char* file_name)
|
||||
{
|
||||
unsigned char header[8]; // 8 is the maximum size that can be checked
|
||||
|
||||
/* open file and test for it being a png */
|
||||
infile = fopen(file_name, "rb");
|
||||
if (infile == NULL)
|
||||
abort_("[read_png_file] File %s could not be opened for reading", file_name);
|
||||
|
||||
fread(header, 1, 8, infile);
|
||||
if (png_sig_cmp(header, 0, 8))
|
||||
abort_("[read_png_file] File %s is not recognized as a PNG file", file_name);
|
||||
|
||||
/* initialize stuff */
|
||||
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
|
||||
if (!png_ptr)
|
||||
abort_("[read_png_file] png_create_read_struct failed");
|
||||
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr)
|
||||
abort_("[read_png_file] png_create_info_struct failed");
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
abort_("[read_png_file] Error during init_io");
|
||||
|
||||
png_init_io(png_ptr, infile);
|
||||
png_set_sig_bytes(png_ptr, 8);
|
||||
|
||||
// Convert palette to RGB values
|
||||
png_set_expand(png_ptr);
|
||||
|
||||
png_read_info(png_ptr, info_ptr);
|
||||
|
||||
width = png_get_image_width(png_ptr, info_ptr);
|
||||
height = png_get_image_height(png_ptr, info_ptr);
|
||||
color_type = png_get_color_type(png_ptr, info_ptr);
|
||||
bit_depth = png_get_bit_depth(png_ptr, info_ptr);
|
||||
|
||||
printf("Input-file is: width=%d, height=%d.\n", width, height);
|
||||
|
||||
number_of_passes = png_set_interlace_handling(png_ptr);
|
||||
png_read_update_info(png_ptr, info_ptr);
|
||||
|
||||
/* read file */
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
abort_("[read_png_file] Error during read_image");
|
||||
|
||||
row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height);
|
||||
for (y=0; y<height; y++)
|
||||
row_pointers[y] = (png_byte*) malloc(png_get_rowbytes(png_ptr,info_ptr));
|
||||
|
||||
png_read_image(png_ptr, row_pointers);
|
||||
|
||||
if (infile != NULL) {
|
||||
fclose(infile);
|
||||
infile = NULL;
|
||||
}
|
||||
|
||||
printf("Input-file is read and now closed\n");
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
|
||||
struct rgb {
|
||||
int r;
|
||||
int g;
|
||||
int b;
|
||||
};
|
||||
|
||||
struct rgb palette[2560];
|
||||
int palette_first=16;
|
||||
int palette_index=16; // only use upper half of palette
|
||||
|
||||
int palette_lookup(int r,int g, int b)
|
||||
{
|
||||
int i;
|
||||
|
||||
// Do we know this colour already?
|
||||
for(i=palette_first;i<palette_index;i++) {
|
||||
if (r==palette[i].r&&g==palette[i].g&&b==palette[i].b) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// new colour
|
||||
if (palette_index>255) {
|
||||
fprintf(stderr,"Too many colours in image: Must be < 256, now up to %d\n",
|
||||
palette_index);
|
||||
}
|
||||
if (palette_index>2559) exit(-1);
|
||||
|
||||
// allocate it
|
||||
palette[palette_index].r=r;
|
||||
palette[palette_index].g=g;
|
||||
palette[palette_index].b=b;
|
||||
return palette_index++;
|
||||
|
||||
}
|
||||
|
||||
unsigned char nyblswap(unsigned char in)
|
||||
{
|
||||
return ((in&0xf)<<4)+((in&0xf0)>>4);
|
||||
}
|
||||
|
||||
void process_file(int mode, char *outputfilename)
|
||||
{
|
||||
int multiplier=-1;
|
||||
if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_RGB)
|
||||
multiplier=3;
|
||||
|
||||
if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_RGBA)
|
||||
multiplier=4;
|
||||
|
||||
if (multiplier==-1) {
|
||||
fprintf(stderr,"Could not convert file to RGB or RGBA\n");
|
||||
}
|
||||
|
||||
outfile=fopen(outputfilename,"w");
|
||||
if (outfile == NULL) {
|
||||
// could not open output file, so close all and exit
|
||||
if (infile != NULL) {
|
||||
fclose(infile);
|
||||
infile = NULL;
|
||||
}
|
||||
abort_("[process_file] File %s could not be opened for writing", outputfilename);
|
||||
}
|
||||
|
||||
|
||||
/* ============================ */
|
||||
|
||||
if (mode==0) {
|
||||
printf("mode=0 (logo)\n");
|
||||
// Logo mode
|
||||
|
||||
// Pre-load in C64 palette, so that those colours can be re-used if required
|
||||
|
||||
palette[0]=(struct rgb){.r=0,.g=0,.b=0};
|
||||
palette[1]=(struct rgb){.r=0xff,.g=0xff,.b=0xff};
|
||||
palette[2]=(struct rgb){.r=0xab,.g=0x31,.b=0x26};
|
||||
palette[3]=(struct rgb){.r=0x66,.g=0xda,.b=0xff};
|
||||
palette[4]=(struct rgb){.r=0xbb,.g=0x3f,.b=0xb8};
|
||||
palette[5]=(struct rgb){.r=0x55,.g=0xce,.b=0x58};
|
||||
palette[6]=(struct rgb){.r=0x1d,.g=0x0e,.b=0x97};
|
||||
palette[7]=(struct rgb){.r=0xea,.g=0xf5,.b=0x7c};
|
||||
palette[8]=(struct rgb){.r=0xb9,.g=0x74,.b=0x18};
|
||||
palette[9]=(struct rgb){.r=0x78,.g=0x73,.b=0x00};
|
||||
palette[10]=(struct rgb){.r=0xdd,.g=0x93,.b=0x87};
|
||||
palette[11]=(struct rgb){.r=0x5b,.g=0x5b,.b=0x5b};
|
||||
palette[12]=(struct rgb){.r=0x8b,.g=0x8b,.b=0x8b};
|
||||
palette[13]=(struct rgb){.r=0xb0,.g=0xf4,.b=0xac};
|
||||
palette[14]=(struct rgb){.r=0xaa,.g=0x9d,.b=0xef};
|
||||
palette[15]=(struct rgb){.r=0xb8,.g=0xb8,.b=0xb8};
|
||||
|
||||
|
||||
for (y=0; y<height; y++) {
|
||||
png_byte* row = row_pointers[y];
|
||||
for (x=0; x<width; x++) {
|
||||
png_byte* ptr = &(row[x*multiplier]);
|
||||
int r,g,b;
|
||||
if (ptr)
|
||||
{ r=ptr[0]; g=ptr[1]; b=ptr[2]; // a=ptr[3];
|
||||
} else { r=0; g=0; b=0; }
|
||||
|
||||
int c=palette_lookup(r,g,b);
|
||||
|
||||
if (c>255) printf("Too many colours at (%d,%d)\n",x,y);
|
||||
|
||||
/* work out where in logo file it must be written.
|
||||
image is made of 8x8 blocks. So every 8 pixels across increases address
|
||||
by 64, and every 8 pixels down increases pixel count by (64*8), and every
|
||||
single pixel down increases address by 8.
|
||||
*/
|
||||
int address=0;
|
||||
address+=0x300; // space for palettes
|
||||
address+=(x&7)+(y&7)*8;
|
||||
address+=(x>>3)*64;
|
||||
// if (size==SIZE_LOGO)
|
||||
address+=(y>>3)*64*(width/8);
|
||||
// else
|
||||
// address+=(y>>3)*64*40;
|
||||
|
||||
fseek(outfile,address,SEEK_SET);
|
||||
int n=fwrite(&c,1,1,outfile);
|
||||
if (n!=1) {
|
||||
fprintf(stderr,"Could not write pixel (%d,%d) @ $%x\n",x,y,address);
|
||||
if (outfile != NULL) {
|
||||
fclose(outfile);
|
||||
outfile = NULL;
|
||||
}
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr,"Writing out palette of %d values\n",palette_index-palette_first);
|
||||
for(int i=0;i<256;i++){
|
||||
int address;
|
||||
unsigned char c;
|
||||
int v;
|
||||
|
||||
address=i+0x000;
|
||||
v=palette[i].r;
|
||||
c=(v>>4)|((v&0xf)<<4);
|
||||
fseek(outfile,address,SEEK_SET);
|
||||
fwrite(&c,1,1,outfile);
|
||||
|
||||
address=i+0x100;
|
||||
v=palette[i].g;
|
||||
c=(v>>4)|((v&0xf)<<4);
|
||||
fseek(outfile,address,SEEK_SET);
|
||||
fwrite(&c,1,1,outfile);
|
||||
|
||||
address=i+0x200;
|
||||
v=palette[i].b;
|
||||
c=(v>>4)|((v&0xf)<<4);
|
||||
fseek(outfile,address,SEEK_SET);
|
||||
fwrite(&c,1,1,outfile);
|
||||
}
|
||||
|
||||
|
||||
if (outfile != NULL) {
|
||||
fclose(outfile);
|
||||
outfile = NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* ============================ */
|
||||
if (mode==1) {
|
||||
|
||||
unsigned char first_half[1024];
|
||||
|
||||
printf("mode=1 (charrom)\n");
|
||||
// charrom mode
|
||||
|
||||
int vhdl_mode=1;
|
||||
if (!strstr(outputfilename,".vhdl")) vhdl_mode=0;
|
||||
|
||||
int bytes=0;
|
||||
if (vhdl_mode) fprintf(outfile,"%s",vhdl_prefix);
|
||||
if (width!=8) {
|
||||
fprintf(stderr,"Fonts must be 8 pixels wide\n");
|
||||
}
|
||||
|
||||
int spots[8][8];
|
||||
int charsets;
|
||||
|
||||
|
||||
// 4KB = 2x 256 char = 2KB charsets
|
||||
for(charsets = 0 ; charsets<2 ; charsets++) {
|
||||
if (bytes>=4096) break;
|
||||
fprintf(stderr,"yheight=%d\n",height);
|
||||
for (y=0; y<height; y++) {
|
||||
png_byte* row = row_pointers[y];
|
||||
int byte=0;
|
||||
int yy=y&7;
|
||||
|
||||
for (x=0; x<width; x++) {
|
||||
png_byte* ptr = &(row[x*multiplier]);
|
||||
int r=ptr[0],g=ptr[1],b=ptr[2]; //, a=ptr[3];
|
||||
|
||||
if (x<8) {
|
||||
if (r>0x7f||g>0x7f||b>0x7f) {
|
||||
byte|=(1<<(7-x));
|
||||
spots[yy][x]=1;
|
||||
} else spots[yy][x]=0;
|
||||
}
|
||||
}
|
||||
fflush(stdout);
|
||||
char comma = ',';
|
||||
if (bytes<1024) {
|
||||
first_half[bytes]=byte;
|
||||
}
|
||||
bytes++;
|
||||
if (bytes>=4096) {
|
||||
comma=' ';
|
||||
}
|
||||
if (vhdl_mode) fprintf(outfile,"x\"%02x\"%c",byte,comma);
|
||||
else fputc(byte,outfile);
|
||||
if (vhdl_mode) {
|
||||
if ((y&7)==7) {
|
||||
fprintf(outfile,"\n");
|
||||
int yy;
|
||||
for(yy=0;yy<8;yy++) {
|
||||
fprintf(outfile,"-- [");
|
||||
for(x=0;x<8;x++) {
|
||||
if (spots[yy][x]) fprintf(outfile,"*"); else fprintf(outfile," ");
|
||||
}
|
||||
fprintf(outfile,"]\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in any missing bytes
|
||||
if (bytes<2048) {
|
||||
|
||||
printf("Padding output file to 2048 after first charset\n");
|
||||
|
||||
if (vhdl_mode) {
|
||||
fprintf(outfile,",\n");
|
||||
|
||||
for(;bytes<2048;bytes+=8) {
|
||||
int reverse=bytes&0x400;
|
||||
if (reverse) reverse=0xff;
|
||||
|
||||
fprintf(outfile,"x\"%02X\",x\"%02X\",x\"%02X\",x\"%02X\",x\"%02X\",x\"%02X\",x\"%02X\",x\"%02X\"%c -- 0x%03x (set %d, char 0x%02x)\n",
|
||||
first_half[(bytes+0)&0x3ff]^reverse,
|
||||
first_half[(bytes+1)&0x3ff]^reverse,
|
||||
first_half[(bytes+2)&0x3ff]^reverse,
|
||||
first_half[(bytes+3)&0x3ff]^reverse,
|
||||
first_half[(bytes+4)&0x3ff]^reverse,
|
||||
first_half[(bytes+5)&0x3ff]^reverse,
|
||||
first_half[(bytes+6)&0x3ff]^reverse,
|
||||
first_half[(bytes+7)&0x3ff]^reverse,
|
||||
',',
|
||||
bytes,bytes/2048,(bytes/8)&0xff);
|
||||
|
||||
fprintf(outfile,"\n");
|
||||
int yy;
|
||||
for(yy=0;yy<8;yy++) {
|
||||
fprintf(outfile,"-- [");
|
||||
for(x=0;x<8;x++) {
|
||||
if ((first_half[(bytes+yy)&0x3ff]^reverse)&(1<<(7-x))) fprintf(outfile,"*"); else fprintf(outfile," ");
|
||||
}
|
||||
fprintf(outfile,"]\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// In raw mode, don't pad, or write charset twice
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// Fill in any missing bytes
|
||||
if (bytes<4096) {
|
||||
|
||||
printf("Padding output file to 4096\n");
|
||||
|
||||
if (vhdl_mode) {
|
||||
fprintf(outfile,",\n");
|
||||
for(;bytes<4096;bytes+=8) {
|
||||
int reverse=bytes&0x400;
|
||||
if (reverse) reverse=0xff;
|
||||
|
||||
fprintf(outfile,"x\"%02X\",x\"%02X\",x\"%02X\",x\"%02X\",x\"%02X\",x\"%02X\",x\"%02X\",x\"%02X\"%c -- 0x%03x (set %d, char %d)\n",
|
||||
first_half[(bytes+0)&0x3ff]^reverse,
|
||||
first_half[(bytes+1)&0x3ff]^reverse,
|
||||
first_half[(bytes+2)&0x3ff]^reverse,
|
||||
first_half[(bytes+3)&0x3ff]^reverse,
|
||||
first_half[(bytes+4)&0x3ff]^reverse,
|
||||
first_half[(bytes+5)&0x3ff]^reverse,
|
||||
first_half[(bytes+6)&0x3ff]^reverse,
|
||||
first_half[(bytes+7)&0x3ff]^reverse,
|
||||
bytes<(4096-8)?',':' ',
|
||||
bytes,bytes/2048,(bytes/8)&0xff);
|
||||
}
|
||||
} else {
|
||||
// In raw mode, don't pad
|
||||
}
|
||||
}
|
||||
if (vhdl_mode) fprintf(outfile,"%s",vhdl_suffix);
|
||||
|
||||
if (outfile != NULL) {
|
||||
fclose(outfile);
|
||||
outfile = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================ */
|
||||
if (mode==2) {
|
||||
printf("mode=2 (hi-res prep)\n");
|
||||
// hi-res image preparation mode
|
||||
|
||||
// int bytes=0;
|
||||
if (width%8||height%8) {
|
||||
fprintf(stderr,"Image must be multiple of 8 pixels wide and high\n");
|
||||
}
|
||||
int problems=0;
|
||||
int total=0;
|
||||
int threes=0;
|
||||
int fours=0;
|
||||
int ones=0;
|
||||
|
||||
int tiles[8000][8][8];
|
||||
int tile_count=0;
|
||||
|
||||
int this_tile[8][8];
|
||||
|
||||
for (y=0; y<height; y+=8) {
|
||||
for (x=0; x<width; x+=8) {
|
||||
int yy,xx;
|
||||
int i;
|
||||
int colour_count=0;
|
||||
int colours[64];
|
||||
|
||||
printf("[%d,%d]\n",x,y);
|
||||
|
||||
total++;
|
||||
|
||||
for(yy=y;yy<y+8;yy++) {
|
||||
png_byte* row = row_pointers[yy];
|
||||
for(xx=x;xx<x+8;xx++) {
|
||||
png_byte* ptr = &(row[xx*multiplier]);
|
||||
int r,g,b;
|
||||
if (ptr&&(yy<height)&&(xx<width)) {
|
||||
r=ptr[0]; g=ptr[1]; b=ptr[2];
|
||||
} else {
|
||||
r=0; g=0; b=0;
|
||||
}
|
||||
int c=r+256*g+65536*b;
|
||||
this_tile[yy-y][xx-x]=c;
|
||||
for(i=0;i<colour_count;i++) if (c==colours[i]) break;
|
||||
if (i==colour_count) {
|
||||
colours[colour_count++]=c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(i=0;i<tile_count;i++) {
|
||||
int dud=0;
|
||||
int xx,yy;
|
||||
for(xx=0;xx<8;xx++)
|
||||
for(yy=0;yy<8;yy++) {
|
||||
if (this_tile[yy][xx]!=tiles[i][yy][xx]) dud=1;
|
||||
}
|
||||
if (!dud) break;
|
||||
}
|
||||
if (i==tile_count) {
|
||||
int xx,yy;
|
||||
for(xx=0;xx<8;xx++)
|
||||
for(yy=0;yy<8;yy++) {
|
||||
tiles[tile_count][yy][xx]=this_tile[yy][xx];
|
||||
}
|
||||
printf(".[%d]",tile_count); fflush(stdout);
|
||||
tile_count++;
|
||||
if (tile_count>=8000) {
|
||||
fprintf(stderr,"Too many tiles\n");
|
||||
if (outfile != NULL) {
|
||||
fclose(outfile);
|
||||
outfile = NULL;
|
||||
}
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if (colour_count==1) ones++;
|
||||
if (colour_count==3) threes++;
|
||||
if (colour_count==4) fours++;
|
||||
if (colour_count>2) {
|
||||
printf("%d colours in card\n",colour_count);
|
||||
problems++;
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("%d problem tiles out of %d total tiles\n",problems,total);
|
||||
printf("%d with 3, %d with 4, %d with only one colour\n",threes,fours,ones);
|
||||
printf("%d unique tiles\n",tile_count);
|
||||
}
|
||||
|
||||
if (mode==3) {
|
||||
// Output set of 16-colour sprites
|
||||
int colour_count=0;
|
||||
int colours[16];
|
||||
fprintf(stderr,"Scanning colour palette...\n");
|
||||
for (y=0; y<height; y++) {
|
||||
for (x=0; x<width; x++) {
|
||||
int i;
|
||||
|
||||
png_byte* row = row_pointers[y];
|
||||
png_byte* ptr = &(row[x*multiplier]);
|
||||
int r=ptr[0], g=ptr[1],b=ptr[2]; // , a=ptr[3];
|
||||
int c=r+256*g+65536*b;
|
||||
for(i=0;i<colour_count;i++) if (c==colours[i]) break;
|
||||
if (i==colour_count) {
|
||||
if (colour_count>=16) {
|
||||
fprintf(stderr,"Too many colours. Image must be 16-colours or less.\n");
|
||||
exit(-1);
|
||||
}
|
||||
colours[colour_count++]=c;
|
||||
}
|
||||
}
|
||||
}
|
||||
fprintf(stderr,"%d unique colours found.\n",colour_count);
|
||||
|
||||
// Avoid colour 0 if we can, to keep it for transparency
|
||||
int colour_offset=0;
|
||||
// if (colour_count<16) colour_offset=1;
|
||||
|
||||
|
||||
int bytes_per_sprite=8*(height>>1);
|
||||
while (bytes_per_sprite%64) bytes_per_sprite++;
|
||||
fprintf(stderr,"Sprites are %d ($%02X) pixels high, and will occupy %d bytes (%d VIC-II sprite slots) each.\n",
|
||||
height/2,height/2,bytes_per_sprite,bytes_per_sprite/64);
|
||||
|
||||
|
||||
// Output palette
|
||||
unsigned char red[16];
|
||||
unsigned char green[16];
|
||||
unsigned char blue[16];
|
||||
int i;
|
||||
|
||||
for(i=0;i<16;i++)
|
||||
{
|
||||
int idx=i;
|
||||
if (colour_offset) idx+=colour_offset;
|
||||
if (idx>=16) idx-=16;
|
||||
|
||||
red[idx]=nyblswap(colours[i]&0xff);
|
||||
green[idx]=nyblswap((colours[i]>>8)&0xff);
|
||||
blue[idx]=nyblswap((colours[i]>>16)&0xff);
|
||||
}
|
||||
// Write magic string, height of sprites in pixels,
|
||||
// and the number of sprites, and how many slots per
|
||||
// sprite
|
||||
fwrite("M65SPRITE16",12,1,outfile);
|
||||
fputc(height/2,outfile);
|
||||
fputc(width/32,outfile);
|
||||
fputc(bytes_per_sprite&0xff,outfile);
|
||||
fputc(bytes_per_sprite>>8,outfile);
|
||||
fwrite(red,16,1,outfile);
|
||||
fwrite(green,16,1,outfile);
|
||||
fwrite(blue,16,1,outfile);
|
||||
|
||||
|
||||
// Output pixels
|
||||
for (x=0; x<width; x+=32) {
|
||||
int bytes_written=0;
|
||||
int xx;
|
||||
fprintf(stderr,"Writing sprite %d\n",x/32);
|
||||
for (y=0; y<height; y+=2) {
|
||||
for(xx=x;xx<(x+32);xx+=4) {
|
||||
unsigned char byte;
|
||||
unsigned char p1,p2;
|
||||
int i;
|
||||
|
||||
png_byte* row = row_pointers[y];
|
||||
png_byte* ptr = &(row[(xx+0)*multiplier]);
|
||||
int r=ptr[0], g=ptr[1],b=ptr[2]; // , a=ptr[3];
|
||||
int c=r+256*g+65536*b;
|
||||
for(i=0;i<colour_count;i++) if (c==colours[i]) break;
|
||||
p1=i;
|
||||
ptr = &(row[(xx+2)*multiplier]);
|
||||
r=ptr[0]; g=ptr[1]; b=ptr[2]; // , a=ptr[3];
|
||||
c=r+256*g+65536*b;
|
||||
for(i=0;i<colour_count;i++) if (c==colours[i]) break;
|
||||
p2=i;
|
||||
byte=(p1<<4)+p2;
|
||||
fputc(byte,outfile);
|
||||
bytes_written++;
|
||||
}
|
||||
}
|
||||
while(bytes_written<bytes_per_sprite) {
|
||||
fputc(0x00,outfile);
|
||||
bytes_written++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 4) {
|
||||
fprintf(stderr,"Usage: program_name <logo|charrom|hires|sprite16> <file_in> <file_out>\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
int mode=-1;
|
||||
|
||||
if (!strcasecmp("logo",argv[1])) mode=0;
|
||||
if (!strcasecmp("charrom",argv[1])) mode=1;
|
||||
if (!strcasecmp("hires",argv[1])) mode=2;
|
||||
if (!strcasecmp("sprite16",argv[1])) mode=3;
|
||||
if (mode==-1) {
|
||||
fprintf(stderr,"Usage: program_name <logo|charrom|hires|sprite16> <file_in> <file_out>\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
printf("argv[0]=%s\n", argv[0]);
|
||||
printf("argv[1]=%s\n", argv[1]);
|
||||
printf("argv[2]=%s\n", argv[2]);
|
||||
printf("argv[3]=%s\n", argv[3]);
|
||||
|
||||
printf("Reading %s\n",argv[2]);
|
||||
read_png_file(argv[2]);
|
||||
|
||||
printf("Processing with mode=%d and output=%s\n", mode, argv[3]);
|
||||
process_file(mode,argv[3]);
|
||||
|
||||
printf("done\n");
|
||||
|
||||
if (infile != NULL) {
|
||||
fclose(infile);
|
||||
infile = NULL;
|
||||
}
|
||||
|
||||
if (outfile != NULL) {
|
||||
fclose(outfile);
|
||||
outfile = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
-615
@@ -1,615 +0,0 @@
|
||||
//
|
||||
// Utility to prepare a release with automatically
|
||||
// assigned version numbers
|
||||
//
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <ios>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
std::string romTypeName;
|
||||
size_t fileSize;
|
||||
size_t signatureOffset1; // offset to 'OR' string
|
||||
size_t signatureOffset2; // offset to 'OR' string
|
||||
size_t idBasicOffset; // offset to BASIC identification string
|
||||
size_t idKernalOffset; // offset to KERNAL identification byte
|
||||
|
||||
} ROMTypeDescriptionEntry;
|
||||
|
||||
const std::vector<ROMTypeDescriptionEntry> ROM_DEFINITIONS =
|
||||
{
|
||||
{
|
||||
"BASIC",
|
||||
8192, // file size
|
||||
0x1F52, // signature offset 1
|
||||
0, // signature offset 2
|
||||
0x0004, // BASIC ID offset
|
||||
0 // KERNAL ID offset
|
||||
},
|
||||
|
||||
{
|
||||
"KERNAL",
|
||||
8192, // file size
|
||||
0x04B9, // signature offset 1
|
||||
0, // signature offset 2
|
||||
0, // BASIC ID offset
|
||||
0x1F80 // KERNAL ID offset
|
||||
},
|
||||
|
||||
{
|
||||
"MEGA65",
|
||||
128 * 1024, // file size
|
||||
0xBF52, // signature offset 1
|
||||
0xE4B9, // signature offset 2
|
||||
0xA004, // BASIC ID offset
|
||||
0xFF80 // KERNAL ID offset
|
||||
},
|
||||
};
|
||||
|
||||
const uint32_t MAX_FILE_SIZE = 128 * 1024;
|
||||
|
||||
const std::vector<uint8_t> STR_MEGABAS = { 0x4D, 0x45, 0x47, 0x41, 0x42, 0x41, 0x53, 0x32 };
|
||||
const std::vector<uint8_t> STR_OR = { 0x4F, 0x52 };
|
||||
const std::vector<uint8_t> STR_SNAPSHOT = { 0x28, 0x44, 0x45, 0x56, 0x45, 0x4C, 0x20, 0x53,
|
||||
0x4E, 0x41, 0x50, 0x53, 0x48, 0x4F, 0x54, 0x29,
|
||||
0x00 };
|
||||
|
||||
const std::string STR_DEV = "DEV.";
|
||||
|
||||
//
|
||||
// Command and enviroment line settings
|
||||
//
|
||||
|
||||
std::string CMD_inDir = "./build";
|
||||
std::string CMD_outDir = "./bin";
|
||||
|
||||
std::list<std::string> CMD_fileList;
|
||||
|
||||
std::string ENV_developerId;
|
||||
std::string ENV_dateString;
|
||||
|
||||
//
|
||||
// Common helper functions
|
||||
//
|
||||
|
||||
void printUsage()
|
||||
{
|
||||
std::cout << "\n" <<
|
||||
"usage: release [-i <input (build) directory>] [-o <output (release) directory>]" << "\n" <<
|
||||
" <file list>" << "\n\n";
|
||||
}
|
||||
|
||||
//
|
||||
// Class definitions
|
||||
//
|
||||
|
||||
class ROMFile
|
||||
{
|
||||
public:
|
||||
|
||||
ROMFile(const std::string &baseFileName);
|
||||
|
||||
void embedRevisionStr(const std::string &newRevisionStr);
|
||||
void save();
|
||||
|
||||
const ROMTypeDescriptionEntry *descPtr;
|
||||
|
||||
bool sameContent;
|
||||
bool sameDeveloper;
|
||||
bool sameDate; // only set if sameDeveloper
|
||||
uint8_t oldDailyRelId; // only set if sameDeveloper and sameDate
|
||||
|
||||
private:
|
||||
|
||||
void readSrcFile();
|
||||
void readDstFile();
|
||||
|
||||
void recognizeSrcFile();
|
||||
void dropUnmatchingDst();
|
||||
void analyzeContent();
|
||||
|
||||
std::string baseFileName;
|
||||
|
||||
std::vector<uint8_t> srcFileContent;
|
||||
std::vector<uint8_t> dstFileContent;
|
||||
};
|
||||
|
||||
//
|
||||
// Global variables
|
||||
//
|
||||
|
||||
size_t GLOBAL_maxFileNameLen = 0;
|
||||
std::list<ROMFile> GLOBAL_ROMFiles;
|
||||
|
||||
//
|
||||
// Top-level functions
|
||||
//
|
||||
|
||||
void parseCommandLine(int argc, char **argv)
|
||||
{
|
||||
int opt;
|
||||
|
||||
// Retrieve command line options
|
||||
|
||||
while ((opt = getopt(argc, argv, "i:o:")) != -1)
|
||||
{
|
||||
switch(opt)
|
||||
{
|
||||
case 'i': CMD_inDir = optarg; break;
|
||||
case 'o': CMD_outDir = optarg; break;
|
||||
default: printUsage(); ERROR();
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve file/directory list
|
||||
|
||||
for (int idx = optind; idx < argc; idx++)
|
||||
{
|
||||
CMD_fileList.push_back(argv[idx]);
|
||||
}
|
||||
|
||||
if (CMD_fileList.empty()) { printUsage(); ERROR("empty file list"); }
|
||||
}
|
||||
|
||||
void getDeveloperId()
|
||||
{
|
||||
const char* id = getenv("OPEN_ROMS_DEVELOPER_ID");
|
||||
|
||||
// Check whether the developer ID is suitable
|
||||
|
||||
if (id == nullptr)
|
||||
{
|
||||
ERROR("environment variable OPEN_ROMS_DEVELOPER_ID not set");
|
||||
}
|
||||
|
||||
ENV_developerId = id;
|
||||
|
||||
if (ENV_developerId.length() < 2 || ENV_developerId.length() > 3)
|
||||
{
|
||||
ERROR("OPEN_ROMS_DEVELOPER_ID has to be 2 or 3 characters long");
|
||||
}
|
||||
|
||||
for (const char &c : ENV_developerId)
|
||||
{
|
||||
if (!(c >= 'A' && c <= 'Z'))
|
||||
{
|
||||
ERROR("OPEN_ROMS_DEVELOPER_ID can only contain uppercase letters");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void getDate()
|
||||
{
|
||||
time_t t = time(NULL);
|
||||
struct tm tm = *localtime(&t);
|
||||
|
||||
const std::string year = std::to_string(tm.tm_year % 100);
|
||||
const std::string month = std::to_string(tm.tm_mon + 1);
|
||||
const std::string day = std::to_string(tm.tm_mday);
|
||||
|
||||
const std::string zero = "0";
|
||||
|
||||
ENV_dateString = (year.size() == 2 ? year : zero + year) +
|
||||
(month.size() == 2 ? month : zero + month) +
|
||||
(day.size() == 2 ? day : zero + day);
|
||||
|
||||
if (ENV_dateString.size() != 6) ERROR("could not retrieve date");
|
||||
}
|
||||
|
||||
void printBanner()
|
||||
{
|
||||
printBannerLineTop();
|
||||
std::cout << "// Preparing ROM set release" << "\n";
|
||||
printBannerLineBottom();
|
||||
}
|
||||
|
||||
void readFiles()
|
||||
{
|
||||
for (auto &fileName : CMD_fileList) GLOBAL_ROMFiles.push_back(ROMFile(fileName));
|
||||
}
|
||||
|
||||
void decideIfUpdate()
|
||||
{
|
||||
// Check if we have any file at all
|
||||
|
||||
if (GLOBAL_ROMFiles.empty())
|
||||
{
|
||||
ERROR("no files to update");
|
||||
}
|
||||
|
||||
// Check if we have at least one file with changed content
|
||||
|
||||
bool sameContent = true;
|
||||
for (auto &file : GLOBAL_ROMFiles)
|
||||
{
|
||||
if (!file.sameContent)
|
||||
{
|
||||
sameContent = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sameContent)
|
||||
{
|
||||
ERROR("same content in all the files");
|
||||
}
|
||||
}
|
||||
|
||||
void saveFiles()
|
||||
{
|
||||
// Determine a daily revision sequence ID
|
||||
|
||||
uint8_t relId = 0;
|
||||
for (auto &file : GLOBAL_ROMFiles)
|
||||
{
|
||||
if (file.sameDeveloper && file.sameDate)
|
||||
{
|
||||
relId = std::max(file.oldDailyRelId, relId);
|
||||
}
|
||||
}
|
||||
relId++;
|
||||
|
||||
if (relId > 9)
|
||||
{
|
||||
ERROR("too many releases this day");
|
||||
}
|
||||
|
||||
// Create a new revision string
|
||||
|
||||
const std::string newRevStr = STR_DEV + ENV_dateString + "." + ENV_developerId + "." + std::to_string(relId);
|
||||
|
||||
for (auto &file : GLOBAL_ROMFiles)
|
||||
{
|
||||
file.embedRevisionStr(newRevStr);
|
||||
}
|
||||
|
||||
std::cout << "Files for release '" << newRevStr << "':\n\n";
|
||||
|
||||
// Save the files
|
||||
|
||||
for (auto &file : GLOBAL_ROMFiles)
|
||||
{
|
||||
file.save();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Main function
|
||||
//
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
parseCommandLine(argc, argv);
|
||||
getDeveloperId();
|
||||
getDate();
|
||||
|
||||
printBanner();
|
||||
|
||||
readFiles();
|
||||
decideIfUpdate();
|
||||
saveFiles();
|
||||
|
||||
std::cout << "\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// Class 'ROMFile'
|
||||
//
|
||||
|
||||
ROMFile::ROMFile(const std::string &baseFileName) :
|
||||
descPtr(nullptr),
|
||||
sameContent(false),
|
||||
sameDeveloper(false),
|
||||
sameDate(false),
|
||||
oldDailyRelId(0),
|
||||
baseFileName(baseFileName)
|
||||
{
|
||||
GLOBAL_maxFileNameLen = std::max(GLOBAL_maxFileNameLen, baseFileName.length());
|
||||
|
||||
readSrcFile();
|
||||
readDstFile();
|
||||
|
||||
recognizeSrcFile();
|
||||
dropUnmatchingDst();
|
||||
analyzeContent();
|
||||
}
|
||||
|
||||
void ROMFile::readSrcFile()
|
||||
{
|
||||
// Read the source file
|
||||
|
||||
const std::string srcFileNamePath = CMD_inDir + DIR_SEPARATOR + baseFileName;
|
||||
|
||||
std::ifstream srcFile;
|
||||
srcFile.open(srcFileNamePath, std::ios::in | std::ios::binary | std::ios::ate);
|
||||
|
||||
if (!srcFile.good()) ERROR(std::string("unable to open ROM file '") + srcFileNamePath + "'");
|
||||
|
||||
auto srcSize = srcFile.tellg();
|
||||
if (srcSize > MAX_FILE_SIZE) ERROR(std::string("incorrect size of ROM file '") + srcFileNamePath + "'");
|
||||
|
||||
srcFileContent.resize(srcSize);
|
||||
srcFile.seekg (0, srcFile.beg);
|
||||
srcFile.read((char *) srcFileContent.data(), srcFileContent.size());
|
||||
|
||||
if (!srcFile.good()) ERROR(std::string("unable to read ROM file '") + srcFileNamePath + "'");
|
||||
srcFile.close();
|
||||
}
|
||||
|
||||
void ROMFile::readDstFile()
|
||||
{
|
||||
// Read the destination file (if exists and it's size matches)
|
||||
|
||||
const std::string dstFileNamePath = CMD_outDir + DIR_SEPARATOR + baseFileName;
|
||||
|
||||
std::ifstream dstFile;
|
||||
dstFile.open(dstFileNamePath, std::ios::in | std::ios::binary | std::ios::ate);
|
||||
|
||||
bool continueReading = dstFile.good();
|
||||
|
||||
if (continueReading && dstFile.tellg() != unsigned(srcFileContent.size())) continueReading = false;
|
||||
|
||||
if (continueReading)
|
||||
{
|
||||
dstFileContent.resize(srcFileContent.size());
|
||||
dstFile.seekg (0, dstFile.beg);
|
||||
dstFile.read((char *) dstFileContent.data(), dstFileContent.size());
|
||||
}
|
||||
|
||||
if (!dstFile.good()) dstFileContent.clear();
|
||||
dstFile.close();
|
||||
}
|
||||
|
||||
void ROMFile::recognizeSrcFile()
|
||||
{
|
||||
// Recognize source file type
|
||||
|
||||
auto checkSignature = [this](size_t signatureOffset) -> bool
|
||||
{
|
||||
if (signatureOffset == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (memcmp(&srcFileContent[signatureOffset], STR_OR.data(), STR_OR.size()) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto snapshotStrOffset = signatureOffset + STR_OR.size() + 1;
|
||||
if (memcmp(&srcFileContent[snapshotStrOffset], STR_SNAPSHOT.data(), STR_SNAPSHOT.size()) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto zeroByteoffset = snapshotStrOffset + 0x10;
|
||||
if (srcFileContent[zeroByteoffset] != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
for (auto &SPEC : ROM_DEFINITIONS)
|
||||
{
|
||||
if (srcFileContent.size() != SPEC.fileSize) continue;
|
||||
|
||||
if ((SPEC.idBasicOffset != 0) &&
|
||||
memcmp(&srcFileContent[SPEC.idBasicOffset], STR_MEGABAS.data(), STR_MEGABAS.size()) != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((SPEC.idKernalOffset != 0) && srcFileContent[SPEC.idKernalOffset] != 0xF0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!checkSignature(SPEC.signatureOffset1) || !checkSignature(SPEC.signatureOffset2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
descPtr = &SPEC;
|
||||
break;
|
||||
}
|
||||
|
||||
if (descPtr == nullptr)
|
||||
{
|
||||
ERROR(std::string("'") + CMD_inDir + DIR_SEPARATOR + baseFileName +
|
||||
"' is not a supported unversioned Open ROMs image");
|
||||
}
|
||||
}
|
||||
|
||||
void ROMFile::dropUnmatchingDst()
|
||||
{
|
||||
// Drop unmatching destination file data
|
||||
|
||||
if (!dstFileContent.empty() && (descPtr->idBasicOffset != 0) &&
|
||||
memcmp(&dstFileContent[descPtr->idBasicOffset], STR_MEGABAS.data(), STR_MEGABAS.size()) != 0)
|
||||
{
|
||||
dstFileContent.clear();
|
||||
}
|
||||
|
||||
if (!dstFileContent.empty() && (descPtr->idKernalOffset != 0) &&
|
||||
dstFileContent[descPtr->idKernalOffset] != 0xF0)
|
||||
{
|
||||
dstFileContent.clear();
|
||||
}
|
||||
|
||||
if (!dstFileContent.empty() && (descPtr->signatureOffset1 != 0) &&
|
||||
memcmp(&dstFileContent[descPtr->signatureOffset1], STR_OR.data(), STR_OR.size()) != 0)
|
||||
{
|
||||
dstFileContent.clear();
|
||||
}
|
||||
|
||||
if (!dstFileContent.empty() && (descPtr->signatureOffset2 != 0) &&
|
||||
memcmp(&dstFileContent[descPtr->signatureOffset2], STR_OR.data(), STR_OR.size()) != 0)
|
||||
{
|
||||
dstFileContent.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void ROMFile::analyzeContent()
|
||||
{
|
||||
auto revOffset1 = (descPtr->signatureOffset1 != 0) ? (descPtr->signatureOffset1 + 3) : 0;
|
||||
auto revOffset2 = (descPtr->signatureOffset2 != 0) ? (descPtr->signatureOffset2 + 3) : 0;
|
||||
|
||||
if (revOffset1 == 0)
|
||||
{
|
||||
revOffset1 = revOffset2;
|
||||
revOffset2 = 0;
|
||||
}
|
||||
|
||||
if (revOffset1 == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (dstFileContent.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If two ID strings are present, they should be the same
|
||||
|
||||
if (0 != revOffset1 && 0 != revOffset2 &&
|
||||
memcmp(&dstFileContent[revOffset1], &dstFileContent[revOffset2], 0x10) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if meaningful file content is really different
|
||||
|
||||
sameContent = true;
|
||||
for (uint32_t idx = 0; idx < descPtr->fileSize; idx++)
|
||||
{
|
||||
if (revOffset1 != 0 && idx - revOffset1 < 0x10)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (revOffset2 != 0 && idx - revOffset2 < 0x10)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (srcFileContent[idx] != dstFileContent[idx])
|
||||
{
|
||||
sameContent = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine expected length of developer ID
|
||||
|
||||
uint8_t devIdLen = ENV_developerId.length();
|
||||
|
||||
// Determine if old version is a devel version compatible with this tool
|
||||
|
||||
if (memcmp(&dstFileContent[revOffset1], STR_DEV.data(), STR_DEV.size()) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t idxDate = revOffset1 + STR_DEV.size();
|
||||
uint16_t idxLimit = idxDate + ENV_dateString.length();
|
||||
|
||||
uint16_t idx = idxDate;
|
||||
for (; idx < idxLimit; idx++)
|
||||
{
|
||||
if (dstFileContent[idx] < '0' || dstFileContent[idx] > '9')
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (dstFileContent[idx] != '.' ||
|
||||
dstFileContent[idx + devIdLen + 1] != '.' ||
|
||||
dstFileContent[idx + devIdLen + 2] < '0' ||
|
||||
dstFileContent[idx + devIdLen + 2] > '9' ||
|
||||
dstFileContent[idx + devIdLen + 3] != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve release data
|
||||
|
||||
if (memcmp(&dstFileContent[idx + 1], ENV_developerId.data(), ENV_developerId.size()) != 0)
|
||||
{
|
||||
return; // different developer
|
||||
}
|
||||
|
||||
sameDeveloper = true;
|
||||
|
||||
if (memcmp(&dstFileContent[idxDate], ENV_dateString.data(), ENV_dateString.size()) != 0)
|
||||
{
|
||||
return; // different date
|
||||
}
|
||||
|
||||
sameDate = true;
|
||||
oldDailyRelId = dstFileContent[idx + devIdLen + 2] - '0';
|
||||
}
|
||||
|
||||
void ROMFile::embedRevisionStr(const std::string &newRevisionStr)
|
||||
{
|
||||
std::string revStr = newRevisionStr;
|
||||
|
||||
while (revStr.length() < 16) revStr = revStr + '\0';
|
||||
|
||||
if (descPtr->signatureOffset1 != 0)
|
||||
{
|
||||
memcpy(&srcFileContent[descPtr->signatureOffset1 + 3],
|
||||
revStr.data(), revStr.size());
|
||||
}
|
||||
|
||||
if (descPtr->signatureOffset2 != 0)
|
||||
{
|
||||
memcpy(&srcFileContent[descPtr->signatureOffset2 + 3],
|
||||
revStr.data(), revStr.size());
|
||||
}
|
||||
}
|
||||
|
||||
void ROMFile::save()
|
||||
{
|
||||
const std::string outFileNamePath = CMD_outDir + DIR_SEPARATOR + baseFileName;
|
||||
|
||||
// Print out file name and metadata
|
||||
|
||||
std::string spacing;
|
||||
spacing.resize(GLOBAL_maxFileNameLen + 4 - baseFileName.length(), ' ');
|
||||
|
||||
std::cout << " " << outFileNamePath << spacing << descPtr->romTypeName;
|
||||
|
||||
if (sameContent)
|
||||
{
|
||||
spacing.resize(10 - descPtr->romTypeName.length());
|
||||
std::cout << spacing << "(unchanged)";
|
||||
}
|
||||
|
||||
std::cout << "\n";
|
||||
|
||||
std::ofstream outFile;
|
||||
outFile.open(outFileNamePath, std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
|
||||
if (!outFile.good()) ERROR(std::string("unable to open output file '") + outFileNamePath + "'");
|
||||
|
||||
outFile.write((char *) srcFileContent.data(), srcFileContent.size());
|
||||
|
||||
if (!outFile.good()) ERROR(std::string("unable to write file '") + outFileNamePath + "'");
|
||||
outFile.close();
|
||||
}
|
||||
@@ -1,279 +0,0 @@
|
||||
/*
|
||||
Simple program to find matching strings between two binaries
|
||||
and report on the statistics of their lengths, and show the
|
||||
longer ones. Used to defend against copyright infringement
|
||||
claims.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <strings.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define MAX_SIZE 65536
|
||||
int matches[MAX_SIZE];
|
||||
|
||||
int phase_mask[MAX_SIZE+MAX_SIZE];
|
||||
|
||||
int main(int argc,char **argv)
|
||||
{
|
||||
int verbose=0;
|
||||
|
||||
if (argc<3) {
|
||||
fprintf(stderr,"usage: similarity <file1> <file2> [verbose]\n");
|
||||
exit(-1);
|
||||
}
|
||||
if (argc==4) {
|
||||
if (!strcmp(argv[3],"verbose")) verbose=1;
|
||||
else {
|
||||
fprintf(stderr,"Unrecognised directive.\n");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
for(int i=0;i<MAX_SIZE;i++) matches[i]=0;
|
||||
bzero(phase_mask,sizeof(phase_mask));
|
||||
|
||||
unsigned char *f1,*f2;
|
||||
int s1,s2;
|
||||
struct stat st;
|
||||
int fd;
|
||||
|
||||
fd = open(argv[1], O_RDONLY, 0);
|
||||
if (fd==-1) {
|
||||
fprintf(stderr,"Could not read '%s'\n",argv[1]);
|
||||
exit(-1);
|
||||
}
|
||||
stat(argv[1], &st);
|
||||
s1=st.st_size;
|
||||
f1 = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE | MAP_POPULATE, fd, 0);
|
||||
if (f1== MAP_FAILED) {
|
||||
fprintf(stderr,"Could not mmap '%s'\n",argv[1]);
|
||||
exit(-1);
|
||||
}
|
||||
fd = open(argv[2], O_RDONLY, 0);
|
||||
if (fd==-1) {
|
||||
fprintf(stderr,"Could not read '%s'\n",argv[2]);
|
||||
exit(-1);
|
||||
}
|
||||
stat(argv[2], &st);
|
||||
s2=st.st_size;
|
||||
f2 = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE | MAP_POPULATE, fd, 0);
|
||||
if (f2 == MAP_FAILED) {
|
||||
fprintf(stderr,"Could not mmap '%s'\n",argv[2]);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
fprintf(stderr,"Searching files for similarities...\n");
|
||||
|
||||
for(int i=0;i<s1;i++) {
|
||||
for(int j=0;j<s2;j++) {
|
||||
int k,l;
|
||||
|
||||
int phase=s2+i-j;
|
||||
|
||||
if (phase<0) {
|
||||
fprintf(stderr,"Phase < 0 : %04x %04x -> %d\n",i,j,phase);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (phase>=(MAX_SIZE+MAX_SIZE)) {
|
||||
fprintf(stderr,"Phase too large : %04x %04x -> %d\n",i,j,phase);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (phase_mask[phase]) {
|
||||
phase_mask[phase]--;
|
||||
continue;
|
||||
}
|
||||
|
||||
for(k=0;((i+k)<s1)&&((j+k)<s2);k++)
|
||||
if (f1[i+k]!=f2[j+k]) break;
|
||||
|
||||
// When we find a match, we should then mask off this region,
|
||||
// so that we don't find all the sub-sets of the matches.
|
||||
// e.g. matching BASIC at $100 will result in a match of ASIC at $101
|
||||
// and SIC at $102 etc. These are all the same match, so should be
|
||||
// suppressed.
|
||||
phase_mask[phase]=k;
|
||||
|
||||
// Ignore matches that are all the same byte
|
||||
for(l=1;l<k;l++) if (f1[i+l]!=f1[i]) break;
|
||||
if (l<k) {
|
||||
// Ignore matches of no more than 3 bytes,
|
||||
// since that is the max length of a 6502 instruction
|
||||
int single_instruction=0;
|
||||
if (k==5) {
|
||||
if (f1[i]==0xa9&&f1[i+2]==0x8d) break; // LDA #$xx / STA $nnnn
|
||||
}
|
||||
if (k==4) {
|
||||
if (f1[i]==0xa9&&f1[i+2]==0x85) break; // LDA #$xx / STA $nn
|
||||
}
|
||||
if (k==3) {
|
||||
// Reject some very common instruction fragments
|
||||
|
||||
|
||||
// Branch followed by any opcode
|
||||
if (f1[i]==0xd0) break;
|
||||
if (f1[i]==0xf0) break;
|
||||
if (f1[i]==0xb0) break;
|
||||
if (f1[i]==0x90) break;
|
||||
if (f1[i]==0x10) break;
|
||||
// Any single byte followed by a branch
|
||||
if (f1[i+1]==0xd0) break;
|
||||
if (f1[i+1]==0xf0) break;
|
||||
if (f1[i+1]==0xb0) break;
|
||||
if (f1[i+1]==0x90) break;
|
||||
if (f1[i+1]==0x10) break;
|
||||
// Any 2-byte opcode followed by a branch
|
||||
if (f1[i+2]==0xd0) break;
|
||||
if (f1[i+2]==0xf0) break;
|
||||
if (f1[i+2]==0xb0) break;
|
||||
if (f1[i+2]==0x90) break;
|
||||
if (f1[i+2]==0x10) break;
|
||||
|
||||
|
||||
// Common 2-byte instructions followed by
|
||||
// any single byte are very common and not
|
||||
// copyrightable.
|
||||
if (f1[i]==0x85) break;
|
||||
if (f1[i]==0xa2) break; // LDX #$xx
|
||||
if (f1[i]==0xa9) break; // LDA #$xx
|
||||
if (f1[i]==0xa5) break; // LDA $xx
|
||||
if (f1[i]==0xa0) break; // LDY #$xx
|
||||
if (f1[i]==0x69) break; // ORA #$xx ?
|
||||
if (f1[i]==0xc9) break; // CMP #$xx
|
||||
if (f1[i]==0x91) break; // STA ($nn),Y
|
||||
// Similarly for random byte before such instructions
|
||||
if (f1[i+1]==0x85) break;
|
||||
if (f1[i+1]==0xa9) break; // LDA #$xx
|
||||
if (f1[i+1]==0xa2) break; // LDX $xx
|
||||
if (f1[i+1]==0xa5) break; // LDA $xx
|
||||
if (f1[i+1]==0xa0) break; // LDY #$xx
|
||||
if (f1[i+1]==0x69) break; // ADC #$xx
|
||||
if (f1[i+1]==0xc9) break; // CMP #$xx
|
||||
|
||||
// Filter out all 3 byte instructions
|
||||
switch(f1[i]) {
|
||||
case 0x0C: // TSB $nnnn
|
||||
case 0x0D: // ORA $nnnn
|
||||
case 0x0E: // ASL $nnnn
|
||||
case 0x19: // ORA $nnnn,Y
|
||||
case 0x1C: // TRB $nnnn
|
||||
case 0x1D: // ORA $nnnn,X
|
||||
case 0x1E: // ASL $nnnn,X
|
||||
case 0x20: // JSR $nnnn
|
||||
case 0x22: // JSR ($nnnn)
|
||||
case 0x23: // JSR ($nnnn,X)
|
||||
case 0x2C: // BIT $nnnn
|
||||
case 0x2D: // AND $nnnn
|
||||
case 0x2E: // ROL $nnnn
|
||||
case 0x39: // AND $nnnn,Y
|
||||
case 0x3C: // BIT $nnnn,X
|
||||
case 0x3D: // AND $nnnn,X
|
||||
case 0x3E: // ROL $nnnn,X
|
||||
case 0x4C: // JMP $nnnn
|
||||
case 0x4D: // EOR $nnnn
|
||||
case 0x4E: // LSR $nnnn
|
||||
case 0x59: // EOR $nnnn,Y
|
||||
case 0x5D: // EOR $nnnn,X
|
||||
case 0x5E: // LSR $nnnn,X
|
||||
case 0x6C: // JMP ($nnnn)
|
||||
case 0x6D: // ADC $nnnn
|
||||
case 0x6E: // ROR $nnnn
|
||||
case 0x79: // ADC $nnnn,Y
|
||||
case 0x7C: // JMP ($nnnn,X)
|
||||
case 0x7D: // ADC $nnnn,X
|
||||
case 0x7E: // ROR $nnnn,X
|
||||
case 0x8B: // STY $nnnn,X
|
||||
case 0x8C: // STY $nnnn
|
||||
case 0x8D: // STA $nnnn
|
||||
case 0x8E: // STX $nnnn
|
||||
case 0x99: // STA $nnnn,Y
|
||||
case 0x9B: // STX $nnnn,Y
|
||||
case 0x9C: // STZ $nnnn
|
||||
case 0x9D: // STA $nnnn,X
|
||||
case 0x9E: // STZ $nnnn,X
|
||||
case 0xAB: // LDZ $nnnn
|
||||
case 0xAC: // LDY $nnnn
|
||||
case 0xAD: // LDA $nnnn
|
||||
case 0xAE: // LDX $nnnn
|
||||
case 0xB9: // LDA $nnnn,Y
|
||||
case 0xBB: // LDZ $nnnn,X
|
||||
case 0xBC: // LDY $nnnn,X
|
||||
case 0xBD: // LDA $nnnn,X
|
||||
case 0xBE: // LDX $nnnn,Y
|
||||
case 0xCB: // ASW $nnnn
|
||||
case 0xCC: // CPY $nnnn
|
||||
case 0xCD: // CMP $nnnn
|
||||
case 0xCE: // DEC $nnnn
|
||||
case 0xD9: // CMP $nnnn,Y
|
||||
case 0xDC: // CPZ $nnnn
|
||||
case 0xDD: // CMP $nnnn,X
|
||||
case 0xDE: // DEC $nnnn,X
|
||||
case 0xEB: // ROW $nnnn
|
||||
case 0xEC: // CPX $nnnn
|
||||
case 0xED: // SBC $nnnn
|
||||
case 0xEE: // INC $nnnn
|
||||
case 0xF4: // PHW #$nnnn
|
||||
case 0xF9: // SBC $nnnn,Y
|
||||
case 0xFC: // PHW $nnnn
|
||||
case 0xFD: // SBC $nnnn,X
|
||||
case 0xFE: // INC $nnnn,X
|
||||
|
||||
single_instruction=1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (single_instruction) break;
|
||||
|
||||
|
||||
if (k>2) {
|
||||
char similarity_name[MAX_SIZE];
|
||||
snprintf(similarity_name,MAX_SIZE,"strings/");
|
||||
int offset=strlen(similarity_name);
|
||||
for(int b=0;b<k;b++) {
|
||||
snprintf(&similarity_name[offset],MAX_SIZE-offset,"%02X",f1[i+b]);
|
||||
offset+=2;
|
||||
}
|
||||
FILE *f=fopen(similarity_name,"r");
|
||||
if (f) {
|
||||
// Get explanation why this match is irrelevant
|
||||
char line[1024]; line[0]=0;
|
||||
fgets(line,1024,f);
|
||||
while (line[0]&&line[strlen(line)-1]=='\r') line[strlen(line)-1]=0;
|
||||
while (line[0]&&line[strlen(line)-1]=='\n') line[strlen(line)-1]=0;
|
||||
if (verbose)
|
||||
fprintf(stderr,"Ignoring $%04X = $%04X + %d (%s)\n",
|
||||
i,j,k,line);
|
||||
break;
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
// Otherwise, the match is unexplained.
|
||||
matches[k]++;
|
||||
|
||||
// Display particularly long matches
|
||||
printf("$%04X = $%04X :",i,j);
|
||||
for(int b=0;b<k;b++) {
|
||||
printf(" %02X",f1[i+b]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(int i=0;i<MAX_SIZE;i++) {
|
||||
if (matches[i])
|
||||
printf("%6d unexplained matches of %d bytes\n",matches[i],i);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user