mirror of
https://github.com/deater/dos33fsprogs.git
synced 2024-08-12 12:29:04 +00:00
Merge branch 'master' of git://github.com/deater/dos33fsprogs
This commit is contained in:
commit
f1aab6e953
@ -1,6 +1,7 @@
|
|||||||
include ../Makefile.inc
|
include ../Makefile.inc
|
||||||
|
|
||||||
all: asoft_detoken tokenize_asoft integer_detoken asoft_compact
|
all: asoft_detoken tokenize_asoft integer_detoken asoft_compact bin2data
|
||||||
|
|
||||||
|
|
||||||
asoft_compact: asoft_compact.o
|
asoft_compact: asoft_compact.o
|
||||||
$(CC) $(LFLAGS) -o asoft_compact asoft_compact.o
|
$(CC) $(LFLAGS) -o asoft_compact asoft_compact.o
|
||||||
@ -14,6 +15,12 @@ asoft_detoken: asoft_detoken.o
|
|||||||
asoft_detoken.o: asoft_detoken.c
|
asoft_detoken.o: asoft_detoken.c
|
||||||
$(CC) $(CFLAGS) -c asoft_detoken.c
|
$(CC) $(CFLAGS) -c asoft_detoken.c
|
||||||
|
|
||||||
|
bin2data: bin2data.o
|
||||||
|
$(CC) $(LFLAGS) -o bin2data bin2data.o
|
||||||
|
|
||||||
|
bin2data.o: bin2data.c
|
||||||
|
$(CC) $(CFLAGS) -c bin2data.c
|
||||||
|
|
||||||
integer_detoken: integer_detoken.o
|
integer_detoken: integer_detoken.o
|
||||||
$(CC) $(LFLAGS) -o integer_detoken integer_detoken.o
|
$(CC) $(LFLAGS) -o integer_detoken integer_detoken.o
|
||||||
|
|
||||||
@ -31,7 +38,7 @@ install:
|
|||||||
cp asoft_detoken tokenize_asoft integer_detoken $(INSTALL_LOC)
|
cp asoft_detoken tokenize_asoft integer_detoken $(INSTALL_LOC)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f *~ *.o asoft_detoken tokenize_asoft \
|
rm -f *~ *.o asoft_detoken tokenize_asoft bin2data \
|
||||||
integer_detoken asoft_compact
|
integer_detoken asoft_compact
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,3 +29,10 @@ asoft_compact: tries to compress your Applesoft basic program
|
|||||||
to make it as small as possible
|
to make it as small as possible
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
bin2data: takes binary image and converts it to suitable
|
||||||
|
BASIC to poke into memory.
|
||||||
|
|
||||||
|
Useful for getting machine language routines usable
|
||||||
|
in BASIC programs
|
||||||
|
|
||||||
|
55
asoft_basic-utils/bin2data.c
Normal file
55
asoft_basic-utils/bin2data.c
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
|
||||||
|
int address=0x300;
|
||||||
|
int bytes=0,line=10,i;
|
||||||
|
struct stat file_info;
|
||||||
|
int fd;
|
||||||
|
unsigned char c;
|
||||||
|
|
||||||
|
if (argc<2) {
|
||||||
|
printf("Usage:\t%s binfile [addr]\n\n",argv[0]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc>2) {
|
||||||
|
address=strtol(argv[2],NULL,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stat(argv[1],&file_info)<0) {
|
||||||
|
fprintf(stderr,"Could not stat file %s\n\n",argv[1]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
bytes=(int)file_info.st_size;
|
||||||
|
|
||||||
|
fd=open(argv[1],O_RDONLY);
|
||||||
|
if (fd<0) {
|
||||||
|
fprintf(stderr,"Could not open file %s\n\n",argv[1]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%d FOR I=0 TO %d: READ X: POKE %d+I,X:NEXT I\n",
|
||||||
|
line,bytes-1,address);
|
||||||
|
line+=10;
|
||||||
|
|
||||||
|
for(i=0;i<bytes;i++) {
|
||||||
|
read(fd,&c,1);
|
||||||
|
|
||||||
|
if (i%16==0) {
|
||||||
|
printf("%d DATA ",line);
|
||||||
|
line+=10;
|
||||||
|
}
|
||||||
|
printf("%d",c);
|
||||||
|
if ((i%16!=15) && (i!=(bytes-1))) printf(",");
|
||||||
|
else printf("\n");
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -14,7 +14,7 @@ dos33: dos33.o
|
|||||||
$(CC) $(LFLAGS) -o dos33 dos33.o
|
$(CC) $(LFLAGS) -o dos33 dos33.o
|
||||||
|
|
||||||
dos33.o: dos33.c dos33.h
|
dos33.o: dos33.c dos33.h
|
||||||
$(CC) $(CFLAGS) -c dos33.c
|
$(CC) $(CFLAGS) -g -c dos33.c
|
||||||
|
|
||||||
dos33_text2ascii: dos33_text2ascii.o
|
dos33_text2ascii: dos33_text2ascii.o
|
||||||
$(CC) $(LFLGAS) -o dos33_text2ascii dos33_text2ascii.o
|
$(CC) $(LFLGAS) -o dos33_text2ascii dos33_text2ascii.o
|
||||||
|
@ -429,10 +429,14 @@ found_one:
|
|||||||
#define ERROR_IMAGE_NOT_FOUND 4
|
#define ERROR_IMAGE_NOT_FOUND 4
|
||||||
#define ERROR_CATALOG_FULL 5
|
#define ERROR_CATALOG_FULL 5
|
||||||
|
|
||||||
|
#define ADD_RAW 0
|
||||||
|
#define ADD_BINARY 1
|
||||||
|
|
||||||
/* creates file apple_filename on the image from local file filename */
|
/* creates file apple_filename on the image from local file filename */
|
||||||
/* returns ?? */
|
/* returns ?? */
|
||||||
static int dos33_add_file(int fd,char type,char *filename,
|
static int dos33_add_file(int fd, char dos_type,
|
||||||
char *apple_filename) {
|
int file_type, int address, int length,
|
||||||
|
char *filename, char *apple_filename) {
|
||||||
|
|
||||||
int free_space,file_size,needed_sectors;
|
int free_space,file_size,needed_sectors;
|
||||||
struct stat file_info;
|
struct stat file_info;
|
||||||
@ -441,6 +445,7 @@ static int dos33_add_file(int fd,char type,char *filename,
|
|||||||
int catalog_track,catalog_sector,sectors_used=0;
|
int catalog_track,catalog_sector,sectors_used=0;
|
||||||
int input_fd;
|
int input_fd;
|
||||||
int result;
|
int result;
|
||||||
|
int first_write=1;
|
||||||
|
|
||||||
if (apple_filename[0]<64) {
|
if (apple_filename[0]<64) {
|
||||||
fprintf(stderr,"Error! First char of filename "
|
fprintf(stderr,"Error! First char of filename "
|
||||||
@ -471,6 +476,12 @@ static int dos33_add_file(int fd,char type,char *filename,
|
|||||||
|
|
||||||
if (debug) printf("Filesize: %d\n",file_size);
|
if (debug) printf("Filesize: %d\n",file_size);
|
||||||
|
|
||||||
|
if (file_type==ADD_BINARY) {
|
||||||
|
if (debug) printf("Adding 4 bytes for size/offset\n");
|
||||||
|
if (length==0) length=file_size;
|
||||||
|
file_size+=4;
|
||||||
|
}
|
||||||
|
|
||||||
/* We need to round up to nearest sector size */
|
/* We need to round up to nearest sector size */
|
||||||
/* Add an extra sector for the T/S list */
|
/* Add an extra sector for the T/S list */
|
||||||
/* Then add extra sector for a T/S list every 122*256 bytes (~31k) */
|
/* Then add extra sector for a T/S list every 122*256 bytes (~31k) */
|
||||||
@ -506,88 +517,105 @@ static int dos33_add_file(int fd,char type,char *filename,
|
|||||||
i=0;
|
i=0;
|
||||||
while (i<size_in_sectors) {
|
while (i<size_in_sectors) {
|
||||||
|
|
||||||
/* Create new T/S list if necessary */
|
/* Create new T/S list if necessary */
|
||||||
if (i%TSL_MAX_NUMBER==0) {
|
if (i%TSL_MAX_NUMBER==0) {
|
||||||
old_ts_list=ts_list;
|
old_ts_list=ts_list;
|
||||||
|
|
||||||
/* allocate a sector for the new list */
|
/* allocate a sector for the new list */
|
||||||
ts_list=dos33_allocate_sector(fd);
|
ts_list=dos33_allocate_sector(fd);
|
||||||
|
sectors_used++;
|
||||||
|
if (ts_list<0) return -1;
|
||||||
|
|
||||||
|
/* clear the t/s sector */
|
||||||
|
for(x=0;x<BYTES_PER_SECTOR;x++) {
|
||||||
|
sector_buffer[x]=0;
|
||||||
|
}
|
||||||
|
lseek(fd,DISK_OFFSET((ts_list>>8)&0xff,ts_list&0xff),SEEK_SET);
|
||||||
|
result=write(fd,sector_buffer,BYTES_PER_SECTOR);
|
||||||
|
|
||||||
|
if (i==0) {
|
||||||
|
initial_ts_list=ts_list;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* we aren't the first t/s list so do special stuff */
|
||||||
|
|
||||||
|
/* load in the old t/s list */
|
||||||
|
lseek(fd,
|
||||||
|
DISK_OFFSET(get_high_byte(old_ts_list),
|
||||||
|
get_low_byte(old_ts_list)),
|
||||||
|
SEEK_SET);
|
||||||
|
|
||||||
|
result=read(fd,§or_buffer,BYTES_PER_SECTOR);
|
||||||
|
|
||||||
|
/* point from old ts list to new one we just made */
|
||||||
|
sector_buffer[TSL_NEXT_TRACK]=get_high_byte(ts_list);
|
||||||
|
sector_buffer[TSL_NEXT_SECTOR]=get_low_byte(ts_list);
|
||||||
|
|
||||||
|
/* set offset into file */
|
||||||
|
sector_buffer[TSL_OFFSET_H]=get_high_byte((i-122)*256);
|
||||||
|
sector_buffer[TSL_OFFSET_L]=get_low_byte((i-122)*256);
|
||||||
|
|
||||||
|
/* write out the old t/s list with updated info */
|
||||||
|
lseek(fd,
|
||||||
|
DISK_OFFSET(get_high_byte(old_ts_list),
|
||||||
|
get_low_byte(old_ts_list)),
|
||||||
|
SEEK_SET);
|
||||||
|
|
||||||
|
result=write(fd,sector_buffer,BYTES_PER_SECTOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* allocate a sector */
|
||||||
|
data_ts=dos33_allocate_sector(fd);
|
||||||
sectors_used++;
|
sectors_used++;
|
||||||
if (ts_list<0) return -1;
|
|
||||||
|
|
||||||
/* clear the t/s sector */
|
if (data_ts<0) return -1;
|
||||||
for(x=0;x<BYTES_PER_SECTOR;x++) sector_buffer[x]=0;
|
|
||||||
lseek(fd,DISK_OFFSET((ts_list>>8)&0xff,ts_list&0xff),SEEK_SET);
|
|
||||||
result=write(fd,sector_buffer,BYTES_PER_SECTOR);
|
|
||||||
|
|
||||||
if (i==0) initial_ts_list=ts_list;
|
|
||||||
else {
|
|
||||||
/* we aren't the first t/s list so do special stuff */
|
|
||||||
|
|
||||||
/* load in the old t/s list */
|
/* clear sector */
|
||||||
lseek(fd,
|
for(x=0;x<BYTES_PER_SECTOR;x++) sector_buffer[x]=0;
|
||||||
DISK_OFFSET(get_high_byte(old_ts_list),
|
|
||||||
get_low_byte(old_ts_list)),
|
|
||||||
SEEK_SET);
|
|
||||||
|
|
||||||
result=read(fd,§or_buffer,BYTES_PER_SECTOR);
|
/* read from input */
|
||||||
|
if ((first_write) && (file_type==ADD_BINARY)) {
|
||||||
/* point from old ts list to new one we just made */
|
first_write=0;
|
||||||
sector_buffer[TSL_NEXT_TRACK]=get_high_byte(ts_list);
|
sector_buffer[0]=address&0xff;
|
||||||
sector_buffer[TSL_NEXT_SECTOR]=get_low_byte(ts_list);
|
sector_buffer[1]=(address>>8)&0xff;
|
||||||
|
sector_buffer[2]=(length)&0xff;
|
||||||
/* set offset into file */
|
sector_buffer[3]=((length)>>8)&0xff;
|
||||||
sector_buffer[TSL_OFFSET_H]=get_high_byte((i-122)*256);
|
bytes_read=read(input_fd,sector_buffer+4,
|
||||||
sector_buffer[TSL_OFFSET_L]=get_low_byte((i-122)*256);
|
BYTES_PER_SECTOR-4);
|
||||||
|
bytes_read+=4;
|
||||||
/* write out the old t/s list with updated info */
|
}
|
||||||
lseek(fd,
|
else {
|
||||||
DISK_OFFSET(get_high_byte(old_ts_list),
|
bytes_read=read(input_fd,sector_buffer,
|
||||||
get_low_byte(old_ts_list)),
|
BYTES_PER_SECTOR);
|
||||||
SEEK_SET);
|
}
|
||||||
|
first_write=0;
|
||||||
|
|
||||||
result=write(fd,sector_buffer,BYTES_PER_SECTOR);
|
if (bytes_read<0) fprintf(stderr,"Error reading bytes!\n");
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* allocate a sector */
|
|
||||||
data_ts=dos33_allocate_sector(fd);
|
|
||||||
sectors_used++;
|
|
||||||
|
|
||||||
if (data_ts<0) return -1;
|
|
||||||
|
|
||||||
/* clear sector */
|
|
||||||
for(x=0;x<BYTES_PER_SECTOR;x++) sector_buffer[x]=0;
|
|
||||||
|
|
||||||
/* read from input */
|
/* write to disk image */
|
||||||
bytes_read=read(input_fd,sector_buffer,BYTES_PER_SECTOR);
|
lseek(fd,DISK_OFFSET((data_ts>>8)&0xff,data_ts&0xff),SEEK_SET);
|
||||||
if (bytes_read<0) fprintf(stderr,"Error reading bytes!\n");
|
result=write(fd,sector_buffer,BYTES_PER_SECTOR);
|
||||||
|
|
||||||
/* write to disk image */
|
if (debug) printf("Writing %i bytes to %i/%i\n",
|
||||||
lseek(fd,DISK_OFFSET((data_ts>>8)&0xff,data_ts&0xff),SEEK_SET);
|
bytes_read,(data_ts>>8)&0xff,data_ts&0xff);
|
||||||
result=write(fd,sector_buffer,BYTES_PER_SECTOR);
|
|
||||||
// printf("Writing %i bytes to %i/%i\n",bytes_read,(data_ts>>8)&0xff,
|
|
||||||
// data_ts&0xff);
|
|
||||||
|
|
||||||
|
|
||||||
|
/* add to T/s table */
|
||||||
/* add to T/s table */
|
|
||||||
|
/* read in t/s list */
|
||||||
/* read in t/s list */
|
lseek(fd,DISK_OFFSET((ts_list>>8)&0xff,ts_list&0xff),SEEK_SET);
|
||||||
lseek(fd,DISK_OFFSET((ts_list>>8)&0xff,ts_list&0xff),SEEK_SET);
|
result=read(fd,sector_buffer,BYTES_PER_SECTOR);
|
||||||
result=read(fd,sector_buffer,BYTES_PER_SECTOR);
|
|
||||||
|
/* point to new data sector */
|
||||||
/* point to new data sector */
|
sector_buffer[((i%TSL_MAX_NUMBER)*2)+TSL_LIST]=(data_ts>>8)&0xff;
|
||||||
sector_buffer[((i%TSL_MAX_NUMBER)*2)+TSL_LIST]=(data_ts>>8)&0xff;
|
sector_buffer[((i%TSL_MAX_NUMBER)*2)+TSL_LIST+1]=(data_ts&0xff);
|
||||||
sector_buffer[((i%TSL_MAX_NUMBER)*2)+TSL_LIST+1]=(data_ts&0xff);
|
|
||||||
|
/* write t/s list back out */
|
||||||
/* write t/s list back out */
|
lseek(fd,DISK_OFFSET((ts_list>>8)&0xff,ts_list&0xff),SEEK_SET);
|
||||||
lseek(fd,DISK_OFFSET((ts_list>>8)&0xff,ts_list&0xff),SEEK_SET);
|
result=write(fd,sector_buffer,BYTES_PER_SECTOR);
|
||||||
result=write(fd,sector_buffer,BYTES_PER_SECTOR);
|
|
||||||
|
i++;
|
||||||
i++;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Add new file to Catalog */
|
/* Add new file to Catalog */
|
||||||
|
|
||||||
@ -632,43 +660,43 @@ continue_parsing_catalog:
|
|||||||
|
|
||||||
goto continue_parsing_catalog;
|
goto continue_parsing_catalog;
|
||||||
|
|
||||||
got_a_dentry:
|
got_a_dentry:
|
||||||
// printf("Adding file at entry %i of catalog 0x%x:0x%x\n",
|
// printf("Adding file at entry %i of catalog 0x%x:0x%x\n",
|
||||||
// i,catalog_track,catalog_sector);
|
// i,catalog_track,catalog_sector);
|
||||||
|
|
||||||
/* Point entry to initial t/s list */
|
|
||||||
sector_buffer[CATALOG_FILE_LIST+(i*CATALOG_ENTRY_SIZE)]=(initial_ts_list>>8)&0xff;
|
|
||||||
sector_buffer[CATALOG_FILE_LIST+(i*CATALOG_ENTRY_SIZE)+1]=(initial_ts_list&0xff);
|
|
||||||
/* set file type */
|
|
||||||
sector_buffer[CATALOG_FILE_LIST+(i*CATALOG_ENTRY_SIZE)+FILE_TYPE]=
|
|
||||||
dos33_char_to_type(type,0);
|
|
||||||
|
|
||||||
// printf("Pointing T/S to %x/%x\n",(initial_ts_list>>8)&0xff,initial_ts_list&0xff);
|
/* Point entry to initial t/s list */
|
||||||
|
sector_buffer[CATALOG_FILE_LIST+(i*CATALOG_ENTRY_SIZE)]=(initial_ts_list>>8)&0xff;
|
||||||
|
sector_buffer[CATALOG_FILE_LIST+(i*CATALOG_ENTRY_SIZE)+1]=(initial_ts_list&0xff);
|
||||||
|
/* set file type */
|
||||||
|
sector_buffer[CATALOG_FILE_LIST+(i*CATALOG_ENTRY_SIZE)+FILE_TYPE]=
|
||||||
|
dos33_char_to_type(dos_type,0);
|
||||||
|
|
||||||
/* copy over filename */
|
// printf("Pointing T/S to %x/%x\n",(initial_ts_list>>8)&0xff,initial_ts_list&0xff);
|
||||||
for(x=0;x<strlen(apple_filename);x++) {
|
|
||||||
sector_buffer[CATALOG_FILE_LIST+(i*CATALOG_ENTRY_SIZE)+FILE_NAME+x]=
|
|
||||||
apple_filename[x]^0x80;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* pad out the filename with spaces */
|
|
||||||
for(x=strlen(apple_filename);x<FILE_NAME_SIZE;x++) {
|
|
||||||
sector_buffer[CATALOG_FILE_LIST+(i*CATALOG_ENTRY_SIZE)+FILE_NAME+x]=' '^0x80;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fill in filesize in sectors */
|
|
||||||
sector_buffer[CATALOG_FILE_LIST+(i*CATALOG_ENTRY_SIZE)+FILE_SIZE_L]=
|
|
||||||
sectors_used&0xff;
|
|
||||||
sector_buffer[CATALOG_FILE_LIST+(i*CATALOG_ENTRY_SIZE)+FILE_SIZE_H]=
|
|
||||||
(sectors_used>>8)&0xff;
|
|
||||||
|
|
||||||
/* write out catalog sector */
|
|
||||||
lseek(fd,DISK_OFFSET(catalog_track,catalog_sector),SEEK_SET);
|
|
||||||
result=write(fd,sector_buffer,BYTES_PER_SECTOR);
|
|
||||||
|
|
||||||
if (result<0) fprintf(stderr,"Error on I/O\n");
|
/* copy over filename */
|
||||||
|
for(x=0;x<strlen(apple_filename);x++) {
|
||||||
return 0;
|
sector_buffer[CATALOG_FILE_LIST+(i*CATALOG_ENTRY_SIZE)+FILE_NAME+x]=
|
||||||
|
apple_filename[x]^0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pad out the filename with spaces */
|
||||||
|
for(x=strlen(apple_filename);x<FILE_NAME_SIZE;x++) {
|
||||||
|
sector_buffer[CATALOG_FILE_LIST+(i*CATALOG_ENTRY_SIZE)+FILE_NAME+x]=' '^0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fill in filesize in sectors */
|
||||||
|
sector_buffer[CATALOG_FILE_LIST+(i*CATALOG_ENTRY_SIZE)+FILE_SIZE_L]=
|
||||||
|
sectors_used&0xff;
|
||||||
|
sector_buffer[CATALOG_FILE_LIST+(i*CATALOG_ENTRY_SIZE)+FILE_SIZE_H]=
|
||||||
|
(sectors_used>>8)&0xff;
|
||||||
|
|
||||||
|
/* write out catalog sector */
|
||||||
|
lseek(fd,DISK_OFFSET(catalog_track,catalog_sector),SEEK_SET);
|
||||||
|
result=write(fd,sector_buffer,BYTES_PER_SECTOR);
|
||||||
|
|
||||||
|
if (result<0) fprintf(stderr,"Error on I/O\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* load a file. fts=entry/track/sector */
|
/* load a file. fts=entry/track/sector */
|
||||||
@ -1174,10 +1202,13 @@ static int dos33_rename_hello(int fd, char *new_name) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int display_help(char *name) {
|
static void display_help(char *name, int version_only) {
|
||||||
printf("\ndos33 version %s\n",VERSION);
|
printf("\ndos33 version %s\n",VERSION);
|
||||||
printf("by Vince Weaver <vince@deater.net>\n");
|
printf("by Vince Weaver <vince@deater.net>\n");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
|
if (version_only) return;
|
||||||
|
|
||||||
printf("Usage: %s [-h] [-y] disk_image COMMAND [options]\n",name);
|
printf("Usage: %s [-h] [-y] disk_image COMMAND [options]\n",name);
|
||||||
printf("\t-h : this help message\n");
|
printf("\t-h : this help message\n");
|
||||||
printf("\t-y : always answer yes for anying warning questions\n");
|
printf("\t-y : always answer yes for anying warning questions\n");
|
||||||
@ -1187,7 +1218,7 @@ static int display_help(char *name) {
|
|||||||
printf("\tCATALOG\n");
|
printf("\tCATALOG\n");
|
||||||
printf("\tLOAD apple_file <local_file>\n");
|
printf("\tLOAD apple_file <local_file>\n");
|
||||||
printf("\tSAVE type local_file <apple_file>\n");
|
printf("\tSAVE type local_file <apple_file>\n");
|
||||||
printf("\tBSAVE type local_file <apple_file>\n");
|
printf("\tBSAVE [-a addr] [-l len] local_file <apple_file>\n");
|
||||||
printf("\tDELETE apple_file\n");
|
printf("\tDELETE apple_file\n");
|
||||||
printf("\tLOCK apple_file\n");
|
printf("\tLOCK apple_file\n");
|
||||||
printf("\tUNLOCK apple_file\n");
|
printf("\tUNLOCK apple_file\n");
|
||||||
@ -1200,24 +1231,26 @@ static int display_help(char *name) {
|
|||||||
printf("\tCOPY\n");
|
printf("\tCOPY\n");
|
||||||
#endif
|
#endif
|
||||||
printf("\n");
|
printf("\n");
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define COMMAND_UNKNOWN 0
|
#define COMMAND_LOAD 0
|
||||||
#define COMMAND_LOAD 1
|
#define COMMAND_SAVE 1
|
||||||
#define COMMAND_SAVE 2
|
#define COMMAND_CATALOG 2
|
||||||
#define COMMAND_CATALOG 3
|
#define COMMAND_DELETE 3
|
||||||
#define COMMAND_DELETE 4
|
#define COMMAND_UNDELETE 4
|
||||||
#define COMMAND_UNDELETE 5
|
#define COMMAND_LOCK 5
|
||||||
#define COMMAND_LOCK 6
|
#define COMMAND_UNLOCK 6
|
||||||
#define COMMAND_UNLOCK 7
|
#define COMMAND_INIT 7
|
||||||
#define COMMAND_INIT 8
|
#define COMMAND_RENAME 8
|
||||||
#define COMMAND_RENAME 9
|
#define COMMAND_COPY 9
|
||||||
#define COMMAND_COPY 10
|
#define COMMAND_DUMP 10
|
||||||
#define COMMAND_DUMP 11
|
#define COMMAND_HELLO 11
|
||||||
#define COMMAND_HELLO 12
|
#define COMMAND_BSAVE 12
|
||||||
#define COMMAND_BSAVE 13
|
#define COMMAND_BLOAD 13
|
||||||
|
|
||||||
#define MAX_COMMAND 14
|
#define MAX_COMMAND 14
|
||||||
|
#define COMMAND_UNKNOWN 255
|
||||||
|
|
||||||
static struct command_type {
|
static struct command_type {
|
||||||
int type;
|
int type;
|
||||||
@ -1242,7 +1275,7 @@ static int lookup_command(char *name) {
|
|||||||
|
|
||||||
int which=COMMAND_UNKNOWN,i;
|
int which=COMMAND_UNKNOWN,i;
|
||||||
|
|
||||||
for(i=1;i<MAX_COMMAND;i++) {
|
for(i=0;i<MAX_COMMAND;i++) {
|
||||||
if(!strncmp(name,commands[i].name,strlen(commands[i].name))) {
|
if(!strncmp(name,commands[i].name,strlen(commands[i].name))) {
|
||||||
which=commands[i].type;
|
which=commands[i].type;
|
||||||
break;
|
break;
|
||||||
@ -1252,6 +1285,21 @@ static int lookup_command(char *name) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int truncate_filename(char *out, char *in) {
|
||||||
|
|
||||||
|
int truncated=0;
|
||||||
|
|
||||||
|
/* Truncate filename if too long */
|
||||||
|
if (strlen(in)>30) {
|
||||||
|
fprintf(stderr,"Warning! Truncating %s to 30 chars\n",in);
|
||||||
|
truncated=1;
|
||||||
|
}
|
||||||
|
strncpy(out,in,30);
|
||||||
|
out[30]='\0';
|
||||||
|
|
||||||
|
return truncated;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
|
|
||||||
char image[BUFSIZ];
|
char image[BUFSIZ];
|
||||||
@ -1261,85 +1309,105 @@ int main(int argc, char **argv) {
|
|||||||
int command,catalog_entry;
|
int command,catalog_entry;
|
||||||
char temp_string[BUFSIZ];
|
char temp_string[BUFSIZ];
|
||||||
char apple_filename[31],new_filename[31];
|
char apple_filename[31],new_filename[31];
|
||||||
char output_filename[BUFSIZ];
|
char local_filename[BUFSIZ];
|
||||||
char *result_string;
|
char *result_string;
|
||||||
int always_yes=0,firstarg=1,extra_ops=0;
|
int always_yes=0;
|
||||||
|
char *temp,*endptr;
|
||||||
|
int c;
|
||||||
|
int address=0, length=0;
|
||||||
|
|
||||||
/* Check command line arguments */
|
/* Check command line arguments */
|
||||||
/* Ugh I should use getopt() or something similar here */
|
while ((c = getopt (argc, argv,"a:l:hvy"))!=-1) {
|
||||||
|
switch (c) {
|
||||||
|
|
||||||
if (argc<2) {
|
case 'a':
|
||||||
display_help(argv[0]);
|
address=strtol(optarg,&endptr,0);
|
||||||
goto exit_program;
|
if (debug) printf("Address=%d\n",address);
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
length=strtol(optarg,&endptr,0);
|
||||||
|
if (debug) printf("Length=%d\n",address);
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
display_help(argv[0],1);
|
||||||
|
return 0;
|
||||||
|
case 'h': display_help(argv[0],0);
|
||||||
|
return 0;
|
||||||
|
case 'y':
|
||||||
|
always_yes=1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strncmp(argv[1],"-h",2)) {
|
if (optind==argc) {
|
||||||
display_help(argv[1]);
|
fprintf(stderr,"ERROR! Must specify disk image!\n\n");
|
||||||
goto exit_program;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
if (!strncmp(argv[1],"-y",2)) {
|
|
||||||
always_yes=1;
|
|
||||||
extra_ops++;
|
|
||||||
firstarg++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argc<3) {
|
|
||||||
printf("\nInvalid arguments!\n");
|
|
||||||
display_help(argv[0]);
|
|
||||||
goto exit_program;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* get argument 1, which is image name */
|
/* get argument 1, which is image name */
|
||||||
strncpy(image,argv[firstarg],BUFSIZ);
|
strncpy(image,argv[optind],BUFSIZ);
|
||||||
dos_fd=open(image,O_RDWR);
|
dos_fd=open(image,O_RDWR);
|
||||||
if (dos_fd<0) {
|
if (dos_fd<0) {
|
||||||
fprintf(stderr,"Error opening disk_image: %s\n",image);
|
fprintf(stderr,"Error opening disk_image: %s\n",image);
|
||||||
exit(4);
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check argument #2 which is command */
|
/* Move to next argument */
|
||||||
strncpy(temp_string,argv[firstarg+1],BUFSIZ);
|
optind++;
|
||||||
|
|
||||||
|
if (optind==argc) {
|
||||||
|
fprintf(stderr,"ERROR! Must specify command!\n\n");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grab command */
|
||||||
|
strncpy(temp_string,argv[optind],BUFSIZ);
|
||||||
|
|
||||||
/* Make command be uppercase */
|
/* Make command be uppercase */
|
||||||
for(i=0;i<strlen(temp_string);i++) {
|
for(i=0;i<strlen(temp_string);i++) {
|
||||||
temp_string[i]=toupper(temp_string[i]);
|
temp_string[i]=toupper(temp_string[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Move to next argument */
|
||||||
|
optind++;
|
||||||
|
|
||||||
command=lookup_command(temp_string);
|
command=lookup_command(temp_string);
|
||||||
|
|
||||||
switch(command) {
|
switch(command) {
|
||||||
|
|
||||||
case COMMAND_UNKNOWN:
|
case COMMAND_UNKNOWN:
|
||||||
display_help(argv[0]);
|
fprintf(stderr,"ERROR! Unknown command %s\n",temp_string);
|
||||||
goto exit_program;
|
fprintf(stderr,"\tTry \"%s -h\" for help.\n\n",argv[0]);
|
||||||
|
goto exit_and_close;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Load a file from disk image to local machine */
|
/* Load a file from disk image to local machine */
|
||||||
case COMMAND_LOAD:
|
case COMMAND_LOAD:
|
||||||
|
|
||||||
/* check and make sure we have apple_filename */
|
/* check and make sure we have apple_filename */
|
||||||
if (argc<4+extra_ops) {
|
if (argc==optind) {
|
||||||
fprintf(stderr,"Error! Need apple file_name\n");
|
fprintf(stderr,"Error! Need apple file_name\n");
|
||||||
fprintf(stderr,"%s %s LOAD apple_filename\n",
|
fprintf(stderr,"%s %s LOAD apple_filename\n",
|
||||||
argv[0],image);
|
argv[0],image);
|
||||||
goto exit_and_close;
|
goto exit_and_close;
|
||||||
}
|
}
|
||||||
/* Truncate filename if too long */
|
|
||||||
if (strlen(argv[firstarg+2])>30) {
|
truncate_filename(apple_filename,argv[optind]);
|
||||||
fprintf(stderr,"Warning! Truncating %s to 30 chars\n",
|
|
||||||
argv[firstarg+2]);
|
if (debug) printf("\tApple filename: %s\n",apple_filename);
|
||||||
}
|
|
||||||
strncpy(apple_filename,argv[firstarg+2],30);
|
|
||||||
apple_filename[30]='\0';
|
|
||||||
|
|
||||||
/* get output filename */
|
/* get output filename */
|
||||||
if (argc==5+extra_ops) {
|
optind++;
|
||||||
strncpy(output_filename,argv[firstarg+3],BUFSIZ);
|
if (argc>=optind) {
|
||||||
|
strncpy(local_filename,argv[optind],BUFSIZ);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
strncpy(output_filename,apple_filename,30);
|
strncpy(local_filename,apple_filename,30);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (debug) printf("\tOutput filename: %s\n",local_filename);
|
||||||
|
|
||||||
|
|
||||||
/* get the entry/track/sector for file */
|
/* get the entry/track/sector for file */
|
||||||
catalog_entry=dos33_check_file_exists(dos_fd,
|
catalog_entry=dos33_check_file_exists(dos_fd,
|
||||||
apple_filename,
|
apple_filename,
|
||||||
@ -1350,243 +1418,276 @@ int main(int argc, char **argv) {
|
|||||||
goto exit_and_close;
|
goto exit_and_close;
|
||||||
}
|
}
|
||||||
|
|
||||||
dos33_load_file(dos_fd,catalog_entry,output_filename);
|
dos33_load_file(dos_fd,catalog_entry,local_filename);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case COMMAND_CATALOG:
|
case COMMAND_CATALOG:
|
||||||
|
|
||||||
/* get first catalog */
|
/* get first catalog */
|
||||||
catalog_entry=dos33_get_catalog_ts(dos_fd);
|
catalog_entry=dos33_get_catalog_ts(dos_fd);
|
||||||
|
|
||||||
printf("\nDISK VOLUME %i\n\n",sector_buffer[VTOC_DISK_VOLUME]);
|
printf("\nDISK VOLUME %i\n\n",sector_buffer[VTOC_DISK_VOLUME]);
|
||||||
while(catalog_entry>0) {
|
while(catalog_entry>0) {
|
||||||
catalog_entry=dos33_find_next_file(dos_fd,catalog_entry);
|
catalog_entry=dos33_find_next_file(dos_fd,catalog_entry);
|
||||||
if (catalog_entry>0) {
|
if (catalog_entry>0) {
|
||||||
dos33_print_file_info(dos_fd,catalog_entry);
|
dos33_print_file_info(dos_fd,catalog_entry);
|
||||||
catalog_entry+=(1<<16);
|
/* why 1<<16 ? */
|
||||||
/* dos33_find_next_file() handles wrapping issues */
|
catalog_entry+=(1<<16);
|
||||||
}
|
/* dos33_find_next_file() handles wrapping issues */
|
||||||
}
|
}
|
||||||
printf("\n");
|
}
|
||||||
break;
|
printf("\n");
|
||||||
|
break;
|
||||||
|
|
||||||
case COMMAND_SAVE:
|
case COMMAND_SAVE:
|
||||||
/* argv3 == type == A,B,T,I,N,L etc */
|
/* argv3 == type == A,B,T,I,N,L etc */
|
||||||
/* argv4 == name of local file */
|
/* argv4 == name of local file */
|
||||||
/* argv5 == optional name of file on disk image */
|
/* argv5 == optional name of file on disk image */
|
||||||
|
|
||||||
if (argc<5+extra_ops) {
|
if (argc==optind) {
|
||||||
fprintf(stderr,"Error! Need type and file_name\n");
|
fprintf(stderr,"Error! Need type and file_name\n");
|
||||||
fprintf(stderr,"%s %s SAVE type "
|
fprintf(stderr,"%s %s SAVE type "
|
||||||
"file_name apple_filename\n",
|
"file_name apple_filename\n\n",
|
||||||
argv[0],image);
|
argv[0],image);
|
||||||
goto exit_and_close;
|
goto exit_and_close;
|
||||||
}
|
}
|
||||||
|
|
||||||
type=argv[firstarg+2][0];
|
type=argv[optind][0];
|
||||||
|
optind++;
|
||||||
|
|
||||||
|
case COMMAND_BSAVE:
|
||||||
|
|
||||||
|
if (debug) printf("\ttype=%c\n",type);
|
||||||
|
|
||||||
|
if (argc==optind) {
|
||||||
|
fprintf(stderr,"Error! Need file_name\n");
|
||||||
|
|
||||||
|
if (command==COMMAND_BSAVE) {
|
||||||
|
fprintf(stderr,"%s %s BSAVE "
|
||||||
|
"file_name apple_filename\n\n",
|
||||||
|
argv[0],image);
|
||||||
|
|
||||||
if (argc==6+extra_ops) {
|
|
||||||
if (strlen(argv[firstarg+4])>30) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"Warning! Truncating filename "
|
|
||||||
"to 30 chars!\n");
|
|
||||||
}
|
}
|
||||||
strncpy(apple_filename,argv[firstarg+4],30);
|
else {
|
||||||
apple_filename[30]=0;
|
fprintf(stderr,"%s %s SAVE type "
|
||||||
|
"file_name apple_filename\n\n",
|
||||||
|
argv[0],image);
|
||||||
|
}
|
||||||
|
goto exit_and_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(local_filename,argv[optind],BUFSIZ);
|
||||||
|
optind++;
|
||||||
|
|
||||||
|
if (debug) printf("\tLocal filename: %s\n",local_filename);
|
||||||
|
|
||||||
|
if (argc>optind) {
|
||||||
|
/* apple filename specified */
|
||||||
|
truncate_filename(apple_filename,argv[optind]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/* If no filename specified for apple name */
|
/* If no filename specified for apple name */
|
||||||
/* Then use the input name. Note, we strip */
|
/* Then use the input name. Note, we strip */
|
||||||
/* everything up to the last slash so useless */
|
/* everything up to the last slash so useless */
|
||||||
/* path info isn't used */
|
/* path info isn't used */
|
||||||
{
|
|
||||||
char *temp;
|
|
||||||
temp=argv[firstarg+3]+(strlen(argv[firstarg+3])-1);
|
|
||||||
|
|
||||||
while(temp!=argv[firstarg+3]) {
|
|
||||||
temp--;
|
|
||||||
if (*temp == '/') {
|
|
||||||
temp++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strlen(temp)>30) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"Warning! Truncating filename to 30 chars!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
strncpy(apple_filename,temp,30);
|
|
||||||
apple_filename[30]=0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
catalog_entry=dos33_check_file_exists(dos_fd,apple_filename,
|
|
||||||
FILE_NORMAL);
|
|
||||||
|
|
||||||
if (catalog_entry>=0) {
|
|
||||||
fprintf(stderr,"Warning! %s exists!\n",apple_filename);
|
|
||||||
if (!always_yes) {
|
|
||||||
printf("Over-write (y/n)?");
|
|
||||||
result_string=fgets(temp_string,BUFSIZ,stdin);
|
|
||||||
if ((result_string==NULL) || (temp_string[0]!='y')) {
|
|
||||||
printf("Exiting early...\n");
|
|
||||||
goto exit_and_close;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fprintf(stderr,"Deleting previous version...\n");
|
|
||||||
dos33_delete_file(dos_fd,catalog_entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
dos33_add_file(dos_fd,type,argv[firstarg+3],apple_filename);
|
temp=local_filename+(strlen(local_filename)-1);
|
||||||
|
|
||||||
|
while(temp!=local_filename) {
|
||||||
|
temp--;
|
||||||
|
if (*temp == '/') {
|
||||||
|
temp++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
truncate_filename(apple_filename,temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug) printf("\tApple filename: %s\n",apple_filename);
|
||||||
|
|
||||||
|
catalog_entry=dos33_check_file_exists(dos_fd,apple_filename,
|
||||||
|
FILE_NORMAL);
|
||||||
|
|
||||||
|
if (catalog_entry>=0) {
|
||||||
|
fprintf(stderr,"Warning! %s exists!\n",apple_filename);
|
||||||
|
if (!always_yes) {
|
||||||
|
printf("Over-write (y/n)?");
|
||||||
|
result_string=fgets(temp_string,BUFSIZ,stdin);
|
||||||
|
if ((result_string==NULL) || (temp_string[0]!='y')) {
|
||||||
|
printf("Exiting early...\n");
|
||||||
|
goto exit_and_close;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(stderr,"Deleting previous version...\n");
|
||||||
|
dos33_delete_file(dos_fd,catalog_entry);
|
||||||
|
}
|
||||||
|
if (command==COMMAND_SAVE) {
|
||||||
|
dos33_add_file(dos_fd,type,
|
||||||
|
ADD_RAW, address, length,
|
||||||
|
local_filename,apple_filename);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dos33_add_file(dos_fd,type,
|
||||||
|
ADD_BINARY, address, length,
|
||||||
|
local_filename,apple_filename);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COMMAND_DELETE:
|
||||||
|
|
||||||
|
if (argc==optind) {
|
||||||
|
fprintf(stderr,"Error! Need file_name\n");
|
||||||
|
fprintf(stderr,"%s %s DELETE apple_filename\n",
|
||||||
|
argv[0],image);
|
||||||
|
goto exit_and_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
truncate_filename(apple_filename,argv[optind]);
|
||||||
|
|
||||||
|
catalog_entry=dos33_check_file_exists(dos_fd,
|
||||||
|
apple_filename,
|
||||||
|
FILE_NORMAL);
|
||||||
|
if (catalog_entry<0) {
|
||||||
|
fprintf(stderr, "Error! File %s does not exist\n",
|
||||||
|
apple_filename);
|
||||||
|
goto exit_and_close;
|
||||||
|
}
|
||||||
|
dos33_delete_file(dos_fd,catalog_entry);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case COMMAND_DELETE:
|
case COMMAND_DUMP:
|
||||||
if (argc+extra_ops<4) {
|
printf("Dumping %s!\n",image);
|
||||||
fprintf(stderr,"Error! Need file_name\n");
|
dos33_dump(dos_fd);
|
||||||
fprintf(stderr,"%s %s DELETE apple_filename\n",argv[0],image);
|
break;
|
||||||
goto exit_and_close;
|
|
||||||
}
|
|
||||||
catalog_entry=dos33_check_file_exists(dos_fd,argv[firstarg+2],
|
|
||||||
FILE_NORMAL);
|
|
||||||
if (catalog_entry<0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"Error! File %s does not exist\n",argv[firstarg+2]);
|
|
||||||
goto exit_and_close;
|
|
||||||
}
|
|
||||||
dos33_delete_file(dos_fd,catalog_entry);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case COMMAND_DUMP:
|
|
||||||
printf("Dumping %s!\n",argv[firstarg]);
|
|
||||||
dos33_dump(dos_fd);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case COMMAND_LOCK:
|
|
||||||
case COMMAND_UNLOCK:
|
|
||||||
/* check and make sure we have apple_filename */
|
|
||||||
if (argc<4+extra_ops) {
|
|
||||||
fprintf(stderr,"Error! Need apple file_name\n");
|
|
||||||
fprintf(stderr,"%s %s LOCK apple_filename\n",argv[0],image);
|
|
||||||
goto exit_and_close;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Truncate filename if too long */
|
|
||||||
if (strlen(argv[firstarg+2])>30) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"Warning! Truncating %s to 30 chars\n",argv[firstarg+2]);
|
|
||||||
}
|
|
||||||
strncpy(apple_filename,argv[firstarg+2],30);
|
|
||||||
apple_filename[30]='\0';
|
|
||||||
|
|
||||||
/* get the entry/track/sector for file */
|
|
||||||
catalog_entry=dos33_check_file_exists(dos_fd,
|
|
||||||
apple_filename,
|
|
||||||
FILE_NORMAL);
|
|
||||||
if (catalog_entry<0) {
|
|
||||||
fprintf(stderr,"Error! %s not found!\n",apple_filename);
|
|
||||||
goto exit_and_close;
|
|
||||||
}
|
|
||||||
|
|
||||||
dos33_lock_file(dos_fd,catalog_entry,command==COMMAND_LOCK);
|
case COMMAND_LOCK:
|
||||||
|
case COMMAND_UNLOCK:
|
||||||
break;
|
/* check and make sure we have apple_filename */
|
||||||
|
if (argc==optind) {
|
||||||
case COMMAND_RENAME:
|
fprintf(stderr,"Error! Need apple file_name\n");
|
||||||
/* check and make sure we have apple_filename */
|
fprintf(stderr,"%s %s %s apple_filename\n",
|
||||||
if (argc<5+extra_ops) {
|
argv[0],image,temp_string);
|
||||||
fprintf(stderr,"Error! Need two filenames\n");
|
goto exit_and_close;
|
||||||
fprintf(stderr,"%s %s LOCK apple_filename_old "
|
}
|
||||||
"apple_filename_new\n",
|
|
||||||
argv[0],image);
|
|
||||||
goto exit_and_close;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Truncate filename if too long */
|
|
||||||
if (strlen(argv[firstarg+2])>30) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"Warning! Truncating %s to 30 chars\n",argv[firstarg+2]);
|
|
||||||
}
|
|
||||||
strncpy(apple_filename,argv[firstarg+2],30);
|
|
||||||
apple_filename[30]='\0';
|
|
||||||
|
|
||||||
/* Truncate filename if too long */
|
|
||||||
if (strlen(argv[firstarg+3])>30) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"Warning! Truncating %s to 30 chars\n",argv[firstarg+3]);
|
|
||||||
}
|
|
||||||
strncpy(new_filename,argv[firstarg+3],30);
|
|
||||||
new_filename[30]='\0';
|
|
||||||
|
|
||||||
/* get the entry/track/sector for file */
|
|
||||||
catalog_entry=dos33_check_file_exists(dos_fd,
|
|
||||||
apple_filename,
|
|
||||||
FILE_NORMAL);
|
|
||||||
if (catalog_entry<0) {
|
|
||||||
fprintf(stderr,"Error! %s not found!\n",apple_filename);
|
|
||||||
goto exit_and_close;
|
|
||||||
}
|
|
||||||
|
|
||||||
dos33_rename_file(dos_fd,catalog_entry,new_filename);
|
truncate_filename(apple_filename,argv[optind]);
|
||||||
|
|
||||||
break;
|
/* get the entry/track/sector for file */
|
||||||
|
catalog_entry=dos33_check_file_exists(dos_fd,
|
||||||
|
apple_filename,
|
||||||
|
FILE_NORMAL);
|
||||||
|
if (catalog_entry<0) {
|
||||||
|
fprintf(stderr,"Error! %s not found!\n",
|
||||||
|
apple_filename);
|
||||||
|
goto exit_and_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
dos33_lock_file(dos_fd,catalog_entry,command==COMMAND_LOCK);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COMMAND_RENAME:
|
||||||
|
/* check and make sure we have apple_filename */
|
||||||
|
if (argc==optind) {
|
||||||
|
fprintf(stderr,"Error! Need two filenames\n");
|
||||||
|
fprintf(stderr,"%s %s LOCK apple_filename_old "
|
||||||
|
"apple_filename_new\n",
|
||||||
|
argv[0],image);
|
||||||
|
goto exit_and_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Truncate filename if too long */
|
||||||
|
truncate_filename(apple_filename,argv[optind]);
|
||||||
|
optind++;
|
||||||
|
|
||||||
|
if (argc==optind) {
|
||||||
|
fprintf(stderr,"Error! Need two filenames\n");
|
||||||
|
fprintf(stderr,"%s %s LOCK apple_filename_old "
|
||||||
|
"apple_filename_new\n",
|
||||||
|
argv[0],image);
|
||||||
|
goto exit_and_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
truncate_filename(new_filename,argv[optind]);
|
||||||
|
|
||||||
|
/* get the entry/track/sector for file */
|
||||||
|
catalog_entry=dos33_check_file_exists(dos_fd,
|
||||||
|
apple_filename,
|
||||||
|
FILE_NORMAL);
|
||||||
|
if (catalog_entry<0) {
|
||||||
|
fprintf(stderr,"Error! %s not found!\n",
|
||||||
|
apple_filename);
|
||||||
|
goto exit_and_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
dos33_rename_file(dos_fd,catalog_entry,new_filename);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case COMMAND_UNDELETE:
|
case COMMAND_UNDELETE:
|
||||||
/* check and make sure we have apple_filename */
|
/* check and make sure we have apple_filename */
|
||||||
if (argc<4) {
|
if (argc==optind) {
|
||||||
fprintf(stderr,"Error! Need apple file_name\n");
|
fprintf(stderr,"Error! Need apple file_name\n");
|
||||||
fprintf(stderr,"%s %s LOCK apple_filename\n",argv[0],image);
|
fprintf(stderr,"%s %s UNDELETE apple_filename\n\n",
|
||||||
|
argv[0],image);
|
||||||
goto exit_and_close;
|
goto exit_and_close;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Truncate filename if too long */
|
/* Truncate filename if too long */
|
||||||
/* what to do about last char ? */
|
/* what to do about last char ? */
|
||||||
if (strlen(argv[firstarg+2])>30) {
|
|
||||||
fprintf(stderr,
|
truncate_filename(apple_filename,argv[optind]);
|
||||||
"Warning! Truncating %s to 30 chars\n",argv[firstarg+2]);
|
|
||||||
}
|
|
||||||
strncpy(apple_filename,argv[firstarg+2],30);
|
|
||||||
apple_filename[30]='\0';
|
|
||||||
|
|
||||||
/* get the entry/track/sector for file */
|
/* get the entry/track/sector for file */
|
||||||
catalog_entry=dos33_check_file_exists(dos_fd,
|
catalog_entry=dos33_check_file_exists(dos_fd,
|
||||||
apple_filename,
|
apple_filename,
|
||||||
FILE_DELETED);
|
FILE_DELETED);
|
||||||
if (catalog_entry<0) {
|
if (catalog_entry<0) {
|
||||||
fprintf(stderr,"Error! %s not found!\n",apple_filename);
|
fprintf(stderr,"Error! %s not found!\n",
|
||||||
|
apple_filename);
|
||||||
goto exit_and_close;
|
goto exit_and_close;
|
||||||
}
|
}
|
||||||
|
|
||||||
dos33_undelete_file(dos_fd,catalog_entry,apple_filename);
|
dos33_undelete_file(dos_fd,catalog_entry,apple_filename);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case COMMAND_HELLO:
|
|
||||||
if (argc+extra_ops<4) {
|
case COMMAND_HELLO:
|
||||||
fprintf(stderr,"Error! Need file_name\n");
|
if (argc==optind) {
|
||||||
fprintf(stderr,"%s %s HELLO apple_filename\n",argv[0],image);
|
fprintf(stderr,"Error! Need file_name\n");
|
||||||
goto exit_and_close;
|
fprintf(stderr,"%s %s HELLO apple_filename\n\n",
|
||||||
}
|
argv[0],image);
|
||||||
catalog_entry=dos33_check_file_exists(dos_fd,argv[firstarg+2],
|
|
||||||
FILE_NORMAL);
|
|
||||||
if (catalog_entry<0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"Warning! File %s does not exist\n",argv[firstarg+2]);
|
|
||||||
}
|
|
||||||
dos33_rename_hello(dos_fd,argv[firstarg+2]);
|
|
||||||
break;
|
|
||||||
case COMMAND_INIT:
|
|
||||||
/* use common code from mkdos33fs? */
|
|
||||||
case COMMAND_COPY:
|
|
||||||
/* use temp file? Walking a sector at a time seems a pain */
|
|
||||||
default:
|
|
||||||
fprintf(stderr,"Sorry, unsupported command\n");
|
|
||||||
goto exit_and_close;
|
goto exit_and_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
truncate_filename(apple_filename,argv[optind]);
|
||||||
|
|
||||||
|
catalog_entry=dos33_check_file_exists(dos_fd,
|
||||||
|
apple_filename,
|
||||||
|
FILE_NORMAL);
|
||||||
|
|
||||||
|
if (catalog_entry<0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Warning! File %s does not exist\n",
|
||||||
|
apple_filename);
|
||||||
|
}
|
||||||
|
dos33_rename_hello(dos_fd,apple_filename);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COMMAND_INIT:
|
||||||
|
/* use common code from mkdos33fs? */
|
||||||
|
case COMMAND_COPY:
|
||||||
|
/* use temp file? Walking a sector at a time seems a pain */
|
||||||
|
default:
|
||||||
|
fprintf(stderr,"Sorry, unsupported command %s\n\n",temp_string);
|
||||||
|
goto exit_and_close;
|
||||||
}
|
}
|
||||||
|
|
||||||
exit_and_close:
|
exit_and_close:
|
||||||
close(dos_fd);
|
close(dos_fd);
|
||||||
exit_program:
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
|
Test hello
|
||||||
|
Test init
|
||||||
Test copy
|
Test copy
|
||||||
Test over-writing existing file
|
Test over-writing existing file
|
||||||
|
@ -4,38 +4,33 @@ MAKEB = ../dos33fs-utils/make_b
|
|||||||
|
|
||||||
all: ethernet.dsk
|
all: ethernet.dsk
|
||||||
|
|
||||||
|
memcpy: memcpy.o
|
||||||
|
ld65 -o memcpy memcpy.o -t none
|
||||||
|
|
||||||
|
memcpy.o: memcpy.s
|
||||||
|
ca65 -o memcpy.o memcpy.s -l memcpy.lst
|
||||||
|
|
||||||
SETUP.BAS: setup.bas
|
SETUP.BAS: setup.bas
|
||||||
$(TXT2BAS) < setup.bas > SETUP.BAS
|
$(TXT2BAS) < setup.bas > SETUP.BAS
|
||||||
|
|
||||||
WEBSERVER.BAS: webserver.bas
|
WEBSERVER.BAS: webserver.bas
|
||||||
$(TXT2BAS) < webserver.bas > WEBSERVER.BAS
|
$(TXT2BAS) < webserver.bas > WEBSERVER.BAS
|
||||||
|
|
||||||
about.html: ./c/about.html
|
|
||||||
$(MAKEB) ./c/about.html about.html 0xc000
|
|
||||||
|
|
||||||
index.html: ./c/index.html
|
|
||||||
$(MAKEB) ./c/index.html index.html 0xc000
|
|
||||||
|
|
||||||
favicon.ico: ./c/favicon.ico
|
|
||||||
$(MAKEB) ./c/favicon.ico favicon.ico 0xc000
|
|
||||||
|
|
||||||
vmw_logo.png: ./c/vmw_logo.png
|
|
||||||
$(MAKEB) ./c/vmw_logo.png vmw_logo.png 0xc000
|
|
||||||
|
|
||||||
R.TXT: request.txt
|
R.TXT: request.txt
|
||||||
$(MAKEB) request.txt R.TXT 0xc000
|
$(MAKEB) request.txt R.TXT 0xc000
|
||||||
|
|
||||||
ethernet.dsk: SETUP.BAS \
|
ethernet.dsk: SETUP.BAS \
|
||||||
WEBSERVER.BAS \
|
WEBSERVER.BAS \
|
||||||
R.TXT \
|
R.TXT \
|
||||||
about.html index.html favicon.ico vmw_logo.png
|
./c/about.html ./c/index.html ./c/favicon.ico ./c/vmw_logo.png
|
||||||
$(DOS33) -y ethernet.dsk SAVE A SETUP.BAS
|
$(DOS33) -y ethernet.dsk SAVE A SETUP.BAS
|
||||||
$(DOS33) -y ethernet.dsk SAVE A WEBSERVER.BAS
|
$(DOS33) -y ethernet.dsk SAVE A WEBSERVER.BAS
|
||||||
$(DOS33) -y ethernet.dsk SAVE B R.TXT
|
$(DOS33) -y ethernet.dsk BSAVE R.TXT
|
||||||
$(DOS33) -y ethernet.dsk SAVE B about.html
|
$(DOS33) -y ethernet.dsk BSAVE -a 0x4000 ./c/about.html
|
||||||
$(DOS33) -y ethernet.dsk SAVE B index.html
|
$(DOS33) -y ethernet.dsk BSAVE -a 0x4000 ./c/index.html
|
||||||
$(DOS33) -y ethernet.dsk SAVE B favicon.ico
|
$(DOS33) -y ethernet.dsk BSAVE -a 0x4000 ./c/favicon.ico
|
||||||
$(DOS33) -y ethernet.dsk SAVE B vmw_logo.png
|
$(DOS33) -y ethernet.dsk BSAVE -a 0x4000 ./c/vmw_logo.png
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f *~ *.BAS R.TXT
|
rm -f *~ *.BAS R.TXT *.o *.lst memcpy
|
||||||
|
|
||||||
|
@ -1,2 +1,29 @@
|
|||||||
Working on getting the Uthernet II card to do something interesting.
|
Working on getting the Uthernet II card to do something interesting.
|
||||||
|
http://a2retrosystems.com/
|
||||||
|
|
||||||
|
Hardware Background:
|
||||||
|
|
||||||
|
This board has a WIZnet W5100 on board. You can get raw Ethernet
|
||||||
|
packets on the board, but I am using it in hardware TCP/IP mode
|
||||||
|
where I set up the MAC/IP and then get raw packets from a TCP socket
|
||||||
|
(up to 4 sockets can be active at once).
|
||||||
|
|
||||||
|
Webserver:
|
||||||
|
Included is a webserver written for Fall 2015 ECE435
|
||||||
|
|
||||||
|
It is written entirely in Applesoft BASIC
|
||||||
|
|
||||||
|
It runs very slowly, but works.
|
||||||
|
|
||||||
|
firefox and wget can get files just fine,
|
||||||
|
although they tend to send duplicate requests for some reason.
|
||||||
|
|
||||||
|
You can serve arbitrary png, jpg, txt, or html files, however
|
||||||
|
they currently have to be less than 8kB.
|
||||||
|
|
||||||
|
Much of the slowness is using peek/poke as a memcpy routine.
|
||||||
|
|
||||||
|
Of course this would all be faster in assembly language, but
|
||||||
|
what's the fun of that.
|
||||||
|
|
||||||
|
|
||||||
|
Binary file not shown.
66
ethernet/memcpy.s
Normal file
66
ethernet/memcpy.s
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
.define EQU =
|
||||||
|
|
||||||
|
PTR EQU $06
|
||||||
|
PTRH EQU $07
|
||||||
|
|
||||||
|
WRAPL EQU $08
|
||||||
|
WRAPH EQU $09
|
||||||
|
|
||||||
|
SIZEL EQU $0A
|
||||||
|
SIZEH EQU $0B
|
||||||
|
|
||||||
|
tx_copy:
|
||||||
|
|
||||||
|
lda #0 ; always copying from 0x4000
|
||||||
|
sta PTR
|
||||||
|
lda #$40
|
||||||
|
sta PTR+1
|
||||||
|
|
||||||
|
ldx #SIZEH ; number of 256-byte blocks
|
||||||
|
beq copy_remainder ; if none, skip ahead
|
||||||
|
|
||||||
|
ldy #0
|
||||||
|
copy256:
|
||||||
|
lda (PTR),y
|
||||||
|
sta $C0B7 ; change based on uthernet slot
|
||||||
|
|
||||||
|
cmp WRAPH,x
|
||||||
|
bne nowrap256
|
||||||
|
|
||||||
|
cmp WRAPL,y
|
||||||
|
bne nowrap256
|
||||||
|
|
||||||
|
lda #$40
|
||||||
|
sta $C0B5
|
||||||
|
lda #$00
|
||||||
|
sta $C0B6 ; wrap tx buffer address to 0x4000
|
||||||
|
|
||||||
|
nowrap256:
|
||||||
|
iny
|
||||||
|
bne copy256
|
||||||
|
|
||||||
|
inc PTR+1 ; update 16-bit pointer
|
||||||
|
dex ; finish a 256 byte block
|
||||||
|
bne copy256
|
||||||
|
|
||||||
|
ldx #SIZEL
|
||||||
|
copy_remainder:
|
||||||
|
lda (PTR),y
|
||||||
|
sta $C0B7 ; change based on uthernet slot
|
||||||
|
|
||||||
|
cmp WRAPL,y
|
||||||
|
bne nowrap_r
|
||||||
|
|
||||||
|
lda #$40
|
||||||
|
sta $C0B5
|
||||||
|
lda #$00
|
||||||
|
sta $C0B6 ; wrap tx buffer address to 0x4000
|
||||||
|
|
||||||
|
nowrap_r:
|
||||||
|
iny
|
||||||
|
dex
|
||||||
|
bne copy_remainder
|
||||||
|
|
||||||
|
rts
|
||||||
|
|
||||||
|
|
46
ethernet/memcpy.txt
Normal file
46
ethernet/memcpy.txt
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
Test 1:
|
||||||
|
5 FOR J=1 to 1000: NEXT J
|
||||||
|
10 PRINT CHR$(7)
|
||||||
|
20 FOR I=16384 to 20479
|
||||||
|
30 POKE 20480,PEEK(I)
|
||||||
|
40 NEXT I
|
||||||
|
100 PRINT CHR$(7)
|
||||||
|
|
||||||
|
Time (linapple2) 38s
|
||||||
|
|
||||||
|
All one one line
|
||||||
|
|
||||||
|
5 FOR J=1 to 1000: NEXT J
|
||||||
|
10 PRINT CHR$(7)
|
||||||
|
20 FOR I=16384 to 20479
|
||||||
|
30 POKE 20480,PEEK(I)
|
||||||
|
40 NEXT I
|
||||||
|
100 PRINT CHR$(7)
|
||||||
|
|
||||||
|
Time (linapple2) 37s
|
||||||
|
|
||||||
|
Assembly language:
|
||||||
|
|
||||||
|
PTR EQU $06
|
||||||
|
|
||||||
|
lda #0
|
||||||
|
sta PTR
|
||||||
|
lda #$40
|
||||||
|
sta PTR+1
|
||||||
|
|
||||||
|
ldx #8
|
||||||
|
ldy #0
|
||||||
|
copy_loop:
|
||||||
|
lda (PTR),y
|
||||||
|
sta $5000
|
||||||
|
iny
|
||||||
|
bne copy_loop
|
||||||
|
dex
|
||||||
|
bne copy_loop
|
||||||
|
|
||||||
|
rts
|
||||||
|
|
||||||
|
Runs more or less instantaenously
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,30 +1,48 @@
|
|||||||
|
' Applesoft BASIC Webserver
|
||||||
|
' by Vince Weaver <vince@deater.net>
|
||||||
|
'
|
||||||
1 REM *** Setup UTHERNET II - W5100
|
1 REM *** Setup UTHERNET II - W5100
|
||||||
2 REM *** Assumes slot 5 ($C0D0)
|
' SLOT0=$C080 49280 SLOT4=$C0C0 49344
|
||||||
3 SLOT=49360: REM *** $C0D0
|
' SLOT1=$C090 49296 SLOT5=$C0D0 49360
|
||||||
4 MR=SLOT+4: REM *** MODE REGISTER C0D4
|
' SLOT2=$C0A0 49312 SLOT6=$C0E0 49376
|
||||||
5 HA=SLOT+5:LA=SLOT+6: REM *** HIGH/LOW ADDR $C0D5,$C0D6
|
' SLOT3=$C0B0 49328 SLOT7=$C0F0 49392
|
||||||
7 DP=SLOT+7: REM *** DATA PORT $C0D7
|
'
|
||||||
|
' Set up the memory addresses to use
|
||||||
|
'
|
||||||
|
2 REM *** OURS IS IN SLOT3 ($C0B0)
|
||||||
|
3 SLOT=49328: REM *** $C0B0
|
||||||
|
4 MR=SLOT+4: REM *** MODE REGISTER C0B4
|
||||||
|
5 HA=SLOT+5:LA=SLOT+6: REM *** HIGH/LOW ADDR $C0B5,$C0B6
|
||||||
|
7 DP=SLOT+7: REM *** DATA PORT $C0B7
|
||||||
|
'
|
||||||
|
' Init the W5100
|
||||||
|
'
|
||||||
10 REM *** Init W5100
|
10 REM *** Init W5100
|
||||||
12 POKE MR,128 : REM RESET W5100
|
12 POKE MR,128 : REM RESET W5100
|
||||||
14 POKE MR,3 : REM AUTOINCREMENT
|
14 POKE MR,3 : REM AUTOINCREMENT
|
||||||
20 REM *** Setup MAC Address
|
20 REM *** Setup MAC Address 41:50:50:4c:45:32
|
||||||
21 REM *** 41:50:50:4c:45:32
|
|
||||||
22 POKE HA,0:POKE LA,9
|
22 POKE HA,0:POKE LA,9
|
||||||
23 POKE DP,65
|
23 POKE DP,65:POKE DP,80:POKE DP,80:POKE DP,76:POKE DP,69:POKE DP,50
|
||||||
23 POKE DP,80
|
30 REM *** Setup IP Address 192.168.8.15
|
||||||
23 POKE DP,80
|
|
||||||
23 POKE DP,76
|
|
||||||
23 POKE DP,69
|
|
||||||
23 POKE DP,50
|
|
||||||
30 REM *** Setup IP Address
|
|
||||||
31 REM *** 192.168.8.15
|
|
||||||
32 POKE LA,15
|
32 POKE LA,15
|
||||||
33 POKE DP,192
|
33 POKE DP,192:POKE DP,168:POKE DP,8:POKE DP,15
|
||||||
34 POKE DP,168
|
|
||||||
35 POKE DP,8
|
|
||||||
36 POKE DP,15
|
|
||||||
40 PRINT "UTHERNET II READY: 192.168.8.15"
|
40 PRINT "UTHERNET II READY: 192.168.8.15"
|
||||||
100 REM *** Setup Socket
|
'
|
||||||
|
' Setup Machine Language Memcpy routine
|
||||||
|
' NOTE! This code assumes the Uthernet is in slot 3
|
||||||
|
' FIXME: patch on the fly once it works
|
||||||
|
' See Appendix 1 at the end of this for more details
|
||||||
|
'
|
||||||
|
50 FOR I=0 TO 72: READ X: POKE 768+I,X:NEXT I
|
||||||
|
51 DATA 169,0,133,6,169,64,133,7,162,11,240,36,160,0,177,6
|
||||||
|
52 DATA 141,183,192,213,9,208,15,217,8,0,208,10,169,64,141,181
|
||||||
|
53 DATA 192,169,0,141,182,192,200,208,229,230,7,202,208,224,162,10
|
||||||
|
54 DATA 177,6,141,183,192,217,8,0,208,10,169,64,141,181,192,169
|
||||||
|
55 DATA 0,141,182,192,200,202,208,232,96
|
||||||
|
'
|
||||||
|
' Setup Socket 0
|
||||||
|
'
|
||||||
|
100 REM *** Setup Socket 0
|
||||||
102 PRINT "** Setting up Socket 0"
|
102 PRINT "** Setting up Socket 0"
|
||||||
105 POKE HA,0:POKE LA,26: REM RX MEMSIZE
|
105 POKE HA,0:POKE LA,26: REM RX MEMSIZE
|
||||||
110 POKE DP,3: REM 8kB RX buffer
|
110 POKE DP,3: REM 8kB RX buffer
|
||||||
@ -36,106 +54,353 @@
|
|||||||
303 PRINT "** Setting up to use TCP port 80"
|
303 PRINT "** Setting up to use TCP port 80"
|
||||||
305 POKE HA,4: POKE LA,4: REM *** 0x404 port
|
305 POKE HA,4: POKE LA,4: REM *** 0x404 port
|
||||||
310 POKE DP,0:POKE DP, 80: REM *** http port 80
|
310 POKE DP,0:POKE DP, 80: REM *** http port 80
|
||||||
|
'
|
||||||
|
' OPEN the socket
|
||||||
|
'
|
||||||
400 REM *** OPEN socket
|
400 REM *** OPEN socket
|
||||||
404 PRINT "** OPENing socket"
|
404 PRINT "** OPENing socket"
|
||||||
405 POKE HA,4: POKE LA,1: REM *** 0x401 command register
|
405 POKE HA,4: POKE LA,1: REM *** 0x401 command register
|
||||||
410 POKE DP, 1: REM *** OPEN
|
410 POKE DP, 1: REM *** OPEN
|
||||||
'
|
'
|
||||||
|
' Check return value
|
||||||
|
'
|
||||||
500 REM *** Check if opened
|
500 REM *** Check if opened
|
||||||
505 POKE HA,4: POKE LA,3: REM *** 0x403 status register
|
505 POKE HA,4: POKE LA,3: REM *** 0x403 status register
|
||||||
510 RE=PEEK(DP)
|
510 RE=PEEK(DP)
|
||||||
515 PRINT "** STATUS IS ";RE;
|
515 PRINT "** STATUS IS ";RE;
|
||||||
520 IF RE=19 THEN PRINT " OPENED":GOTO 600
|
520 IF RE=19 THEN PRINT " OPENED":GOTO 600
|
||||||
530 IF RE=0 THEN PRINT " CLOSED, ERROR"
|
530 IF RE=0 THEN PRINT " CLOSED, ERROR": GOTO 5000
|
||||||
540 POKE HA,4: POKE LA,1: REM *** 0x401 command register
|
540 PRINT "UNKNOWN ERROR ";RE
|
||||||
550 POKE DP, 16: REM *** CLOSE
|
550 GOTO 5000
|
||||||
560 END
|
'
|
||||||
|
' LISTEN on the socket
|
||||||
'
|
'
|
||||||
600 REM *** Connection opened, Listen
|
600 REM *** Connection opened, Listen
|
||||||
605 POKE HA,4: POKE LA,1: REM *** 0x401 command register
|
605 POKE HA,4: POKE LA,1: REM *** 0x401 command register
|
||||||
610 POKE DP, 2: REM *** LISTEN
|
610 POKE DP, 2: REM *** LISTEN
|
||||||
|
'
|
||||||
|
' Check return value
|
||||||
|
'
|
||||||
620 REM *** Check if successful
|
620 REM *** Check if successful
|
||||||
625 POKE HA,4: POKE LA,3: REM *** 0x403 status register
|
625 POKE HA,4: POKE LA,3: REM *** 0x403 status register
|
||||||
630 RE=PEEK(DP)
|
630 RE=PEEK(DP)
|
||||||
635 PRINT "** STATUS IS ";RE;
|
635 PRINT "** STATUS IS ";RE;
|
||||||
640 IF RE=20 THEN PRINT " LISTENING":GOTO 700
|
640 IF RE=20 THEN PRINT " LISTENING":GOTO 700
|
||||||
650 IF RE=0 THEN PRINT " CLOSED, ERROR"
|
650 IF RE=0 THEN PRINT " CLOSED, ERROR":GOTO 5000
|
||||||
655 PRINT "UNKNOWN"
|
655 PRINT "UNKNOWN ERROR ";RE
|
||||||
660 POKE HA,4: POKE LA,1: REM *** 0x401 command register
|
675 GOTO 5000
|
||||||
670 POKE DP, 16: REM *** CLOSE
|
'
|
||||||
675 END
|
' Wait for incoming connection
|
||||||
'
|
'
|
||||||
700 REM *** Wait for incoming connection
|
700 REM *** Wait for incoming connection
|
||||||
705 POKE HA,4: POKE LA,1: REM *** 0x401 command register
|
705 POKE HA,4: POKE LA,1: REM *** 0x401 command register
|
||||||
710 POKE DP, 2: REM *** LISTEN
|
710 POKE DP, 2: REM *** LISTEN
|
||||||
|
'
|
||||||
|
' Check for result
|
||||||
|
'
|
||||||
720 REM *** Check if successful
|
720 REM *** Check if successful
|
||||||
725 POKE HA,4: POKE LA,3: REM *** 0x403 status register
|
725 POKE HA,4: POKE LA,3: REM *** 0x403 status register
|
||||||
730 RE=PEEK(DP)
|
730 RE=PEEK(DP)
|
||||||
740 IF RE=23 THEN GOTO 800
|
740 IF RE=23 THEN GOTO 800: REM ESTABLISHED
|
||||||
745 IF RE<>20 THEN PRINT "WAITING: STATUS=";RE
|
745 IF RE<>20 THEN PRINT "WAITING: UNEXPECTED STATUS=";RE
|
||||||
750 GOTO 700: REM *** Repeat until connected
|
750 GOTO 700: REM *** Repeat until connected
|
||||||
'
|
'
|
||||||
800 PRINT "CONNECTED"
|
' Established, repeat waiting for incoming data
|
||||||
|
'
|
||||||
|
800 PRINT "ESTABLISHED"
|
||||||
802 POKE HA,4: POKE LA,38: REM *** 0x426 Received Size
|
802 POKE HA,4: POKE LA,38: REM *** 0x426 Received Size
|
||||||
805 SH=PEEK(DP):SL=PEEK(DP)
|
805 SH=PEEK(DP):SL=PEEK(DP)
|
||||||
810 SI=(SH*256)+SL
|
810 SI=(SH*256)+SL
|
||||||
820 IF SI<>0 THEN GOTO 900
|
820 IF SI<>0 THEN GOTO 900
|
||||||
|
'
|
||||||
|
' Should we delay? busy polling seems wasteful
|
||||||
|
'
|
||||||
830 REM DELAY?
|
830 REM DELAY?
|
||||||
840 GOTO 800
|
840 GOTO 802
|
||||||
|
'
|
||||||
|
' We have some data, let's read it
|
||||||
'
|
'
|
||||||
900 POKE HA,4: POKE LA,40: REM *** 0x428 Received ptr
|
900 POKE HA,4: POKE LA,40: REM *** 0x428 Received ptr
|
||||||
905 OH=PEEK(DP):OL=PEEK(DP)
|
905 OH=PEEK(DP):OL=PEEK(DP)
|
||||||
910 RO=(OH*256)+OL
|
910 RF=(OH*256)+OL
|
||||||
920 REM *** SHOULD MASK WITH 0x1ff but how?
|
920 REM *** MASK WITH 0x1ff
|
||||||
930 RA=RO+24576:REM $6000
|
925 R%=RF/8192:RM=RF-(8192*R%)
|
||||||
940 PRINT "READ OFFSET=";RO;" READ ADDRESS=";RA;" READ SIZE=";SI
|
930 RA=RM+24576:REM $6000
|
||||||
|
940 PRINT "READ OFFSET=";RM;" READ ADDRESS=";RA;" READ SIZE=";SI
|
||||||
|
'
|
||||||
|
' Check for buffer wraparound
|
||||||
|
'
|
||||||
|
942 BW=0
|
||||||
|
945 IF (SI+TA>=32768) THEN BW=1:BO=32768-TA:PRINT "RX BUFFER WRAPAROUND IN ";BO
|
||||||
|
'
|
||||||
|
' Print received packet
|
||||||
'
|
'
|
||||||
1000 REM *** PRINT PACKET
|
1000 REM *** PRINT PACKET
|
||||||
1005 POKE HA,RA/256: POKE LA,RA-((RA/256)*256)
|
1001 FL=1:FL$=""
|
||||||
|
1003 R%=RA/256
|
||||||
|
1005 POKE HA,R%: POKE LA,RA-(R%*256)
|
||||||
1010 FOR I=1 TO SI
|
1010 FOR I=1 TO SI
|
||||||
1020 C=PEEK(DP)
|
1020 C=PEEK(DP):C$=CHR$(C)
|
||||||
1030 IF C<>13 THEN PRINT CHR$(C);
|
1025 IF FL=1 THEN FL$=FL$+C$
|
||||||
|
1027 IF C=10 THEN FL=0
|
||||||
|
1030 IF C<>10 THEN PRINT C$;
|
||||||
|
1032 IF BW=0 THEN GOTO 1040
|
||||||
|
1033 BO=BO-1: IF BO=0 THEN POKE HA,96:POKE LA,0:BW=0
|
||||||
1040 NEXT I
|
1040 NEXT I
|
||||||
'
|
'
|
||||||
|
' Deal with first line
|
||||||
|
'
|
||||||
|
1050 PRINT "FIRST LINE=";FL$
|
||||||
|
1060 IF LEFT$(FL$,3)<>"GET" GOTO 7000
|
||||||
|
1065 N$=""
|
||||||
|
1070 FOR I=6 TO LEN(FL$)
|
||||||
|
1075 M$=MID$(FL$,I,1)
|
||||||
|
1080 IF M$=" " GOTO 1090
|
||||||
|
1085 N$=N$+M$
|
||||||
|
1087 NEXT I
|
||||||
|
1090 IF N$="" THEN N$="index.html"
|
||||||
|
1095 PRINT "SENDING FILE: ";N$
|
||||||
|
'
|
||||||
' TODO: handle wraparound of 8kb buffer
|
' TODO: handle wraparound of 8kb buffer
|
||||||
'
|
'
|
||||||
|
'
|
||||||
|
' Update read pointer
|
||||||
|
'
|
||||||
1100 REM *** Update read pointer
|
1100 REM *** Update read pointer
|
||||||
'"HTTP/1.1 200 OK\r\n"
|
1110 POKE HA,4: POKE LA,40: REM *** 0x428 Received ptr
|
||||||
'"Date: %s\r\n"
|
1120 RA=RF+SI
|
||||||
'"Server: VMW-web\r\n"
|
1130 R%=RA/256
|
||||||
'"Last-Modified: %s\r\n"
|
1140 POKE DP,R%: POKE DP,RA-(R%*256)
|
||||||
'"Content-Length: %ld\r\n"
|
1150 REM *** RECEIVE
|
||||||
'"Content-Type: %s\r\n"
|
1160 POKE HA,4: POKE LA,1: REM *** 0x401 command register
|
||||||
'"\r\n",
|
1170 POKE DP, 64: REM *** RECV
|
||||||
1200 REM *** SEND RESPONSE
|
'
|
||||||
1205 A$="HTTP/1.1 200 OK"+CHR$(13)+CHR$(10)
|
' Load file from disk
|
||||||
1210 A$=A$+"Server: VMW-web"+CHR$(13)+CHR$(10)
|
'
|
||||||
1220 A$=A$+"Content-Length: 65"+CHR$(13)+CHR$(10)
|
1200 REM *** LOAD FILE
|
||||||
1230 A$=A$+"Content-Type: text/html"+CHR$(13)+CHR$(10)
|
1202 X$=RIGHT$(N$,3):M$="text/html"
|
||||||
1250 A$=A$+CHR$(13)+CHR$(10)
|
1203 IF X$="txt" THEN M$="text/plain"
|
||||||
1260 A$=A$+"<html><head>test</head><body><h3>Apple2 Test</h3></body></html>"
|
1204 IF X$="png" THEN M$="image/png"
|
||||||
1270 A$=A$+CHR$(13)+CHR$(10)
|
1205 IF X$="jpg" THEN M$="image/jpg"
|
||||||
1280 PRINT "SENDING:":PRINT A$
|
1206 IF X$="ico" THEN M$="image/x-icon"
|
||||||
' TODO: read TX free size reg (0x420)
|
1207 IF N$="teapot.html" GOTO 9000
|
||||||
1900 POKE HA,4: POKE LA,34: REM *** 0x422 TX read ptr
|
1208 ONERR GOTO 8000
|
||||||
|
1209 PRINT "LOADING ";N$
|
||||||
|
1210 PRINT CHR$(4)+"BLOAD ";N$
|
||||||
|
1215 POKE 216,0: REM CANCEL ONERR
|
||||||
|
1220 FS=PEEK(43616)+256*PEEK(43617): REM FILESIZE
|
||||||
|
1225 PRINT "DONE LOADING"
|
||||||
|
' assume loaded at 0x4000, text page 2
|
||||||
|
' and that max size is 8kb
|
||||||
|
1240 A$="HTTP/1.1 200 OK"+CHR$(13)+CHR$(10)
|
||||||
|
1250 A$=A$+"Server: VMW-web"+CHR$(13)+CHR$(10)
|
||||||
|
1260 A$=A$+"Content-Length: "+STR$(FS)+CHR$(13)+CHR$(10)
|
||||||
|
1280 A$=A$+"Content-Type: "+M$+CHR$(13)+CHR$(10)+CHR$(13)+CHR$(10)
|
||||||
|
'
|
||||||
|
1380 PRINT "SENDING:":PRINT A$
|
||||||
|
1385 C=0
|
||||||
|
'
|
||||||
|
' read TX free size reg (0x420)
|
||||||
|
'
|
||||||
|
1700 SI=LEN(A$)+FS
|
||||||
|
1710 IF (SI>8192) THEN PRINT "FILE TOO BIG!": REM GOTO 403?
|
||||||
|
1800 POKE HA,4: POKE LA,32: REM *** 0x420 FREESIZE
|
||||||
|
1810 OH=PEEK(DP):OL=PEEK(DP)
|
||||||
|
1815 FR=(OH*256)+OL
|
||||||
|
1820 PRINT "FREE: ";FR
|
||||||
|
1830 IF SI>FR GOTO 1800: REM REPEAT UNTIL FREE
|
||||||
|
'
|
||||||
|
' Read tx offset
|
||||||
|
'
|
||||||
|
1900 POKE HA,4: POKE LA,36: REM *** 0x424 TX write ptr
|
||||||
1905 OH=PEEK(DP):OL=PEEK(DP)
|
1905 OH=PEEK(DP):OL=PEEK(DP)
|
||||||
1910 TF=(OH*256)+OL
|
1910 TF=(OH*256)+OL
|
||||||
1920 REM *** SHOULD MASK WITH 0x1ff
|
1920 REM *** MASK WITH 0x1ff
|
||||||
1925 T%=TF/8192:TF=TF-(8192*T%)
|
1925 T%=TF/8192:TM=TF-(8192*T%)
|
||||||
1930 TA=TF+16384:REM $4000
|
1930 TA=TM+16384:REM $4000
|
||||||
1935 SI=LEN(A$)
|
1940 PRINT "OH/OL=";OH;"/";OL;" TX OFFSET=";TM;" TX ADDRESS=";TA;" TX SIZE=";SI
|
||||||
1940 PRINT "TX OFFSET=";TF;" TX ADDRESS=";TA;" TX SIZE=";SI
|
'
|
||||||
|
' Check for buffer wraparound
|
||||||
|
'
|
||||||
|
1942 BW=0:BO=0
|
||||||
|
1945 IF (SI+TA>=24576) THEN BW=1:BO=24576-TA:PRINT "TX BUFFER WRAPAROUND IN ";BO
|
||||||
|
'
|
||||||
|
' Write data to TX buffer
|
||||||
|
' First write header
|
||||||
|
'
|
||||||
2000 T%=TA/256
|
2000 T%=TA/256
|
||||||
2005 POKE HA,T%: POKE LA,TA-(T%*256)
|
2005 POKE HA,T%: POKE LA,TA-(T%*256)
|
||||||
2010 FOR I=1 TO SI
|
2010 FOR I=1 TO LEN(A$)
|
||||||
2020 POKE DP,ASC(MID$(A$,I,1))
|
2015 POKE DP,ASC(MID$(A$,I,1))
|
||||||
2040 NEXT I
|
2017 IF BW=0 THEN GOTO 2020
|
||||||
|
2018 BO=BO-1: IF BO=0 THEN POKE HA,64:POKE LA,0:BW=0
|
||||||
|
2020 NEXT I
|
||||||
|
'
|
||||||
|
' Write disk part
|
||||||
|
'
|
||||||
|
2025 FOR I=1 TO FS
|
||||||
|
2026 C=C+1: IF C=50 THEN PRINT ".";:C=0
|
||||||
|
2030 POKE DP,PEEK(16383+I)
|
||||||
|
2032 IF BW=0 THEN GOTO 2035
|
||||||
|
2033 BO=BO-1: IF BO=0 THEN POKE HA,64:POKE LA,0:BW=0
|
||||||
|
2035 NEXT I
|
||||||
|
2040 PRINT
|
||||||
|
'
|
||||||
|
' The above is slow
|
||||||
|
' Intead use our machine language routine
|
||||||
|
'
|
||||||
|
'2025 B%=BO/256:POKE 9,B%:POKE 8,BO-(B%*256)
|
||||||
|
'2027 B%=FS/256:POKE 11,B%:POKE 10,FS-(B%*256)
|
||||||
|
'2030 CALL 768
|
||||||
|
'
|
||||||
|
' Update TX write ptr
|
||||||
|
'
|
||||||
2050 REM ** UPDATE TX WRITE PTR
|
2050 REM ** UPDATE TX WRITE PTR
|
||||||
2060 POKE HA,4: POKE LA,36: REM *** 0x424 TX write ptr
|
2060 POKE HA,4: POKE LA,36: REM *** 0x424 TX write ptr
|
||||||
2075 TA=TA+SI
|
2075 TA=TF+SI
|
||||||
2080 T%=TA/256
|
2080 T%=TA/256
|
||||||
2085 POKE HA,T%: POKE LA,TA-(T%*256)
|
2085 POKE DP,T%: POKE DP,TA-(T%*256)
|
||||||
|
2090 PRINT "UPDATE TX TO ";T%;"/";TA-(T%*256)
|
||||||
|
'
|
||||||
|
' SEND packet
|
||||||
|
'
|
||||||
2100 REM *** SEND
|
2100 REM *** SEND
|
||||||
|
2102 PRINT "SENDING"
|
||||||
2105 POKE HA,4: POKE LA,1: REM *** 0x401 command register
|
2105 POKE HA,4: POKE LA,1: REM *** 0x401 command register
|
||||||
2110 POKE DP, 32: REM *** SEND
|
2110 POKE DP, 32: REM *** SEND
|
||||||
5000 REM *** CLOSE
|
'
|
||||||
|
' Return to reading
|
||||||
|
'
|
||||||
|
4000 REM *** Check if successful
|
||||||
|
4010 POKE HA,4: POKE LA,3: REM *** 0x403 status register
|
||||||
|
4020 RE=PEEK(DP)
|
||||||
|
4030 PRINT "STATUS AFTER SEND ";RE
|
||||||
|
4035 IF RE=28 THEN GOTO 6000: REM CLOSE_WAIT
|
||||||
|
4040 IF RE=0 THEN GOTO 400: REM CLOSED
|
||||||
|
4060 REM *** RECEIVE
|
||||||
|
4075 POKE HA,4: POKE LA,1: REM *** 0x401 command register
|
||||||
|
4080 POKE DP, 64: REM *** RECV
|
||||||
|
4090 GOTO 800
|
||||||
|
'
|
||||||
|
' Close the socket
|
||||||
|
'
|
||||||
|
5000 REM *** CLOSE AND EXIT
|
||||||
|
5010 POKE HA,4: POKE LA,1: REM *** 0x401 command register
|
||||||
|
5020 POKE DP, 16: REM *** CLOSE
|
||||||
|
5030 END
|
||||||
|
6000 REM *** CLOSE AND RELISTEN
|
||||||
|
6010 POKE HA,4: POKE LA,1: REM *** 0x401 command register
|
||||||
|
6020 POKE DP, 16: REM *** CLOSE
|
||||||
|
' Check status?
|
||||||
|
6030 GOTO 400
|
||||||
|
'
|
||||||
|
'
|
||||||
|
' ERROR MESSAGES
|
||||||
|
'
|
||||||
|
'
|
||||||
|
7000 REM 400 BAD REQUEST
|
||||||
|
7005 S$="400 Bad Request"
|
||||||
|
7010 M$="<html><head><title>400 Bad Request</title></head><body><h1>Bad Request</h1><p>Your browser sent a request that this server could not understand.<br /></p></body></html>"+CHR$(13)+CHR$(10)
|
||||||
|
7020 GOTO 9100
|
||||||
|
8000 REM 404 NOT FOUND
|
||||||
|
8003 POKE 216,0: REM CANCEL ONERR
|
||||||
|
8004 PRINT "DISK ERROR: ";PEEK(222)
|
||||||
|
8005 S$="404 Not Found"
|
||||||
|
8010 M$="<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>File not found.<br /></p></body></html>"+CHR$(13)+CHR$(10)
|
||||||
|
8020 GOTO 9100
|
||||||
|
9000 REM 418 TEAPOT
|
||||||
|
9005 S$="418 I'm a Teapot"
|
||||||
|
9010 M$="<html><head><title>418 I'm a Teapot</title></head><body><h1>I'm a Teapot</h1><p>Short *and* stout.<br /></p></body></html>"+CHR$(13)+CHR$(10)
|
||||||
|
'
|
||||||
|
' Make header
|
||||||
|
'
|
||||||
|
9100 A$="HTTP/1.1 "+S$+CHR$(13)+CHR$(10)+"Server: VMW-web"+CHR$(13)+CHR$(10)
|
||||||
|
9105 A$=A$+"Content-Length: "+STR$(LEN(M$))+CHR$(13)+CHR$(10)
|
||||||
|
9110 A$=A$+"Connection: close"+CHR$(13)+CHR$(10)+"Content-Type: text/html; charset=iso-8859-1"+CHR$(13)+CHR$(10)+CHR$(13)+CHR$(10)
|
||||||
|
' Poke as if we had loaded from disk
|
||||||
|
9200 FS=LEN(M$)
|
||||||
|
9210 FOR I=1 TO FS
|
||||||
|
9220 POKE 16383+I,ASC(MID$(M$,I,1))
|
||||||
|
9300 NEXT I
|
||||||
|
9310 GOTO 1380
|
||||||
|
'
|
||||||
|
' STATUSES
|
||||||
|
' p28 of W5100 manual
|
||||||
|
' 0x0 0 SOCK_CLOSED
|
||||||
|
' 0x13 SOCK_INIT
|
||||||
|
' 0x14 SOCK_LISTEN
|
||||||
|
' 0x17 23 SOCK_ESTABLISHED
|
||||||
|
' 0x1C 28 SOCK_CLOSE_WAIT
|
||||||
|
' 0x22 SOCK_UDP
|
||||||
|
' 0x32 SOCK_IPRAW
|
||||||
|
' 0x42 SOCK_MACRAW
|
||||||
|
' 0x5f SOCK_PPOE
|
||||||
|
|
||||||
|
'
|
||||||
|
' Appendix 1: The memcpy machine code
|
||||||
|
'
|
||||||
|
'
|
||||||
|
'PTR EQU $06
|
||||||
|
'PTRH EQU $07
|
||||||
|
'
|
||||||
|
'WRAPL EQU $08
|
||||||
|
'WRAPH EQU $09
|
||||||
|
'
|
||||||
|
'SIZEL EQU $0A
|
||||||
|
'SIZEH EQU $0B
|
||||||
|
'
|
||||||
|
'tx_copy:
|
||||||
|
'
|
||||||
|
' lda #0 ; always copying from 0x4000
|
||||||
|
' sta PTR
|
||||||
|
' lda #$40
|
||||||
|
' sta PTR+1
|
||||||
|
'
|
||||||
|
' ldx #SIZEH ; number of 256-byte blocks
|
||||||
|
' beq copy_remainder ; if none, skip ahead
|
||||||
|
'
|
||||||
|
' ldy #0
|
||||||
|
'copy256:
|
||||||
|
' lda (PTR),y
|
||||||
|
' sta $C0B7 ; change based on uthernet slot
|
||||||
|
'
|
||||||
|
' cmp WRAPH,x
|
||||||
|
' bne nowrap256
|
||||||
|
'
|
||||||
|
' cmp WRAPL,y
|
||||||
|
' bne nowrap256
|
||||||
|
'
|
||||||
|
' lda #$40
|
||||||
|
' sta $C0B5
|
||||||
|
' lda #$00
|
||||||
|
' sta $C0B6 ; wrap tx buffer address to 0x4000
|
||||||
|
'
|
||||||
|
'nowrap256:
|
||||||
|
' iny
|
||||||
|
' bne copy256
|
||||||
|
'
|
||||||
|
' inc PTR+1 ; update 16-bit pointer
|
||||||
|
' dex ; finish a 256 byte block
|
||||||
|
' bne copy256
|
||||||
|
'
|
||||||
|
' ldx #SIZEL
|
||||||
|
'copy_remainder:
|
||||||
|
' lda (PTR),y
|
||||||
|
' sta $C0B7 ; change based on uthernet slot
|
||||||
|
'
|
||||||
|
' cmp WRAPL,y
|
||||||
|
' bne nowrap_r
|
||||||
|
'
|
||||||
|
' lda #$40
|
||||||
|
' sta $C0B5
|
||||||
|
' lda #$00
|
||||||
|
' sta $C0B6 ; wrap tx buffer address to 0x4000
|
||||||
|
'
|
||||||
|
'nowrap_r:
|
||||||
|
' iny
|
||||||
|
' dex
|
||||||
|
' bne copy_remainder
|
||||||
|
'
|
||||||
|
' rts
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user