1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-11-02 08:30:55 +11:00

Compare commits

...

5 Commits

Author SHA1 Message Date
Virgil Dupras
9edab10a3a blk: add dirty flag and auto write blocks on fetch
Also, fix some PSP leaks related to LOAD.
2020-04-16 20:59:20 -04:00
Virgil Dupras
79ce88c12c tools: add blkunpack
and remove cfspack, which will not ever be used again.
2020-04-16 19:44:17 -04:00
Virgil Dupras
5cb4a7de9a Add word "C@+" 2020-04-16 18:58:11 -04:00
Virgil Dupras
eefa8e6de5 Add word "BLK!"
as well as emulator support for it. We can now write an edited
block back to "blkfs".
2020-04-16 17:22:18 -04:00
Virgil Dupras
57e20f0532 Block explorer upgraded to block editor! 2020-04-16 15:59:43 -04:00
52 changed files with 359 additions and 406 deletions

View File

@ -4,7 +4,7 @@ This is a Forth-style filesystems which is very simple. It is a
list of 1024 bytes block, organised in 16 lines of 64 columns list of 1024 bytes block, organised in 16 lines of 64 columns
each. You refer to blocks by numbers. You show them with LIST. each. You refer to blocks by numbers. You show them with LIST.
You interpret them with LOAD. For a convenient way to browse You interpret them with LOAD. For a convenient way to browse
blocks, see Block Explorer at B100. blocks, see Block editor at B100.
Conventions: When you see "(cont.)" at the bottom right of a Conventions: When you see "(cont.)" at the bottom right of a
block, it means that the next block continues the same kind of block, it means that the next block continues the same kind of

14
blk/001
View File

@ -1,4 +1,16 @@
MASTER INDEX MASTER INDEX
3 Usage 30 Dictionary 3 Usage 30 Dictionary
70 Implementation notes 100 Block explorer 70 Implementation notes 100 Block editor

View File

@ -10,3 +10,7 @@ Contents
4 DOES> 6 Compilation vs meta-comp. 4 DOES> 6 Compilation vs meta-comp.
8 I/O 11 Chained comparisons 8 I/O 11 Chained comparisons
14 Addressed devices 18 Signed-ness 14 Addressed devices 18 Signed-ness

View File

@ -13,3 +13,4 @@ At compile time, colon definition stops processing words when
reaching the DOES>. reaching the DOES>.
Example: ": CONSTANT CREATE HERE @ ! DOES> @ ;" Example: ": CONSTANT CREATE HERE @ ! DOES> @ ;"

View File

@ -12,3 +12,5 @@ new "(parse)" word.
This way, we have a full-featured (and extensible) parsing with This way, we have a full-featured (and extensible) parsing with
a tiny native core. a tiny native core.

12
blk/016
View File

@ -2,3 +2,15 @@
kind of ad-hoc way to have some kind of mapping function, but kind of ad-hoc way to have some kind of mapping function, but
what you'll mostly want to to is to plug device drivers into what you'll mostly want to to is to plug device drivers into
those words. those words.

View File

@ -7,3 +7,10 @@ support the "-" prefix, but under the hood, it's all unsigned.
This leads to some oddities. For example, "-1 0 <" is false. This leads to some oddities. For example, "-1 0 <" is false.
To compare whether something is negative, use the "<0" word To compare whether something is negative, use the "<0" word
which is the equivalent to "0x7fff >". which is the equivalent to "0x7fff >".

View File

@ -9,3 +9,8 @@ Be sure to read usage guide (B3) first.
52 Addressed devices 54 Arithmetic / Bits 52 Addressed devices 54 Arithmetic / Bits
56 Logic 58 Strings 56 Logic 58 Strings
60 I/O 64 Disk 60 I/O 64 Disk

View File

@ -8,3 +8,9 @@ Words between "()" are "support words" that aren't really meant
to be used directly, but as part of another word. to be used directly, but as part of another word.
"*I*" in description indicates an IMMEDIATE word. "*I*" in description indicates an IMMEDIATE word.

13
blk/035
View File

@ -1,3 +1,16 @@
(cont.) (cont.)
~ - Container for native code. Usually not an executable word. ~ - Container for native code. Usually not an executable word.
? - Is it ...? (example: IMMED?) ? - Is it ...? (example: IMMED?)

10
blk/038
View File

@ -4,3 +4,13 @@ FORGET x -- Rewind the dictionary (both CURRENT and HERE)
PREV a -- a Return a wordref's previous entry. PREV a -- a Return a wordref's previous entry.
WHLEN a -- n Get word header length from wordref. That is, WHLEN a -- n Get word header length from wordref. That is,
name length + 3. a is a wordref name length + 3. a is a wordref

View File

@ -13,3 +13,4 @@ INTERPRET -- Get a line from stdin, compile it in tmp memory,
then execute the compiled contents. then execute the compiled contents.
QUIT -- Return to interpreter prompt immediately QUIT -- Return to interpreter prompt immediately
EXIT! -- Exit current INTERPRET loop. EXIT! -- Exit current INTERPRET loop.

View File

View File

@ -9,3 +9,8 @@ SWAP a b -- b a
2DUP a b -- a b a b 2DUP a b -- a b a b
2OVER a b c d -- a b c d a b 2OVER a b c d -- a b c d a b
2SWAP a b c d -- c d a b 2SWAP a b c d -- c d a b

View File

@ -5,3 +5,12 @@ R> R:n -- n Pops RS and push to PS
I -- n Copy RS TOS to PS I -- n Copy RS TOS to PS
I' -- n Copy RS second item to PS I' -- n Copy RS second item to PS
J -- n Copy RS third item to PS J -- n Copy RS third item to PS

View File

@ -5,6 +5,7 @@ Memory
? a -- Print value of addr a ? a -- Print value of addr a
+! n a -- Increase value of addr a by n +! n a -- Increase value of addr a by n
C@ a -- c Set c to byte at address a C@ a -- c Set c to byte at address a
C@+ a -- a+1 c Fetch c from a and inc a.
C! c a -- Store byte c in address a C! c a -- Store byte c in address a
CURRENT -- a Set a to wordref of last added entry. CURRENT -- a Set a to wordref of last added entry.
CURRENT* -- a A pointer to active CURRENT*. Useful CURRENT* -- a A pointer to active CURRENT*. Useful

View File

@ -8,3 +8,9 @@ A! c a -- Indirect C!
A@* -- a Address for A@ word A@* -- a Address for A@ word
A!* -- a Address for A! word A!* -- a Address for A! word
AMOVE src dst u -- Same as MOVE, but with A@ and A! AMOVE src dst u -- Same as MOVE, but with A@ and A!

View File

@ -12,3 +12,5 @@ OR a b -- c a | b -> c
XOR a b -- c a ^ b -> c XOR a b -- c a ^ b -> c
Shortcuts: 1+ 2+ 1- 2- Shortcuts: 1+ 2+ 1- 2-

View File

@ -6,3 +6,11 @@ Logic
CMP n1 n2 -- n Compare n1 and n2 and set n to -1, 0, or 1. CMP n1 n2 -- n Compare n1 and n2 and set n to -1, 0, or 1.
n=0: a1=a2. n=1: a1>a2. n=-1: a1<a2. n=0: a1=a2. n=1: a1>a2. n=-1: a1<a2.
NOT f -- f Push the logical opposite of f NOT f -- f Push the logical opposite of f

View File

@ -8,3 +8,9 @@ LITS a -- Write word at addr a as a atring literal.
S= a1 a2 -- f Returns whether string a1 == a2. S= a1 a2 -- f Returns whether string a1 == a2.
SCPY a -- Copy string at addr a into HERE. SCPY a -- Copy string at addr a into HERE.
SLEN a -- n Push length of str at a. SLEN a -- n Push length of str at a.

View File

@ -7,3 +7,10 @@ SPC

10
blk/064
View File

@ -4,3 +4,13 @@ BLK> -- a Address of the current block variable.
LIST n -- Prints the contents of the block n on screen in the LIST n -- Prints the contents of the block n on screen in the
form of 16 lines of 64 columns. form of 16 lines of 64 columns.
LOAD n -- Interprets Forth code from block n LOAD n -- Interprets Forth code from block n

10
blk/070
View File

@ -4,3 +4,13 @@ Implementation notes
75 Stack management 77 Dictionary 75 Stack management 77 Dictionary
80 System variables 85 Word routines 80 System variables 85 Word routines
89 Initialization sequence 89 Initialization sequence

View File

@ -9,3 +9,8 @@ it. As a general rule, we go like this:
4. Is it a number? 4. Is it a number?
5. If yes, push that number to PS, goto 1 5. If yes, push that number to PS, goto 1
6. Error: undefined word. 6. Error: undefined word.

View File

@ -12,3 +12,5 @@ IX always points to RS' Top Of Stack (TOS)
This return stack contain "Interpreter pointers", that is a This return stack contain "Interpreter pointers", that is a
pointer to the address of a word, as seen in a compiled list of pointer to the address of a word, as seen in a compiled list of
words. words.

View File

@ -10,3 +10,7 @@ chain). There are also "special words", for example NUMBER,
LIT, FBR, that have a slightly different structure. They're LIT, FBR, that have a slightly different structure. They're
also a pointer to an executable, but as for the other fields, also a pointer to an executable, but as for the other fields,
the only one they have is the "flags" field. the only one they have is the "flags" field.

10
blk/084
View File

@ -4,3 +4,13 @@ DRIVERS section is reserved for recipe-specific
drivers. Here is a list of known usages: drivers. Here is a list of known usages:
* 0x70-0x78: ACIA buffer pointers in RC2014 recipes. * 0x70-0x78: ACIA buffer pointers in RC2014 recipes.

10
blk/087
View File

@ -4,3 +4,13 @@ being followed by a 2 byte number, it is followed by a
null-terminated string. Upon execution, the address of that null-terminated string. Upon execution, the address of that
null-terminated string is pushed on the PSP and IP is advanced null-terminated string is pushed on the PSP and IP is advanced
to the address following the null. to the address following the null.

View File

@ -5,3 +5,12 @@ for space with HERE: New entries to the dict will overwrite
that code! Also, because we're barebone, we can't have that code! Also, because we're barebone, we can't have
comments. This can lead to peculiar code in this area where we comments. This can lead to peculiar code in this area where we
try to "waste" space in initialization code. try to "waste" space in initialization code.

18
blk/100
View File

@ -1,10 +1,16 @@
Block explorer Block editor
This is an application to conveniently browse the contents of This is an application to conveniently browse the contents of
the disk blocks. You can launch it with "102 LOAD". the disk blocks and edit them. You can load it with "102 LOAD".
USAGE: When loaded, the Forth interpreter is replaced by the Browse mode: If you execute BROWSE, the Forth interpreter is
explorer interpreter. Typing "Q" quits the program. replaced by browser's loop. Typing "Q" quits the browser.
Typing a decimal number followed by space or return lists the In this mode, typing a decimal number followed by space or
contents of that block. B for previous block, N for next. return lists the contents of that block. B for previous block,
N for next.
When not in browse mode, your prompt is a regular Forth prompt
with editor words loaded.
(cont.)

16
blk/101 Normal file
View File

@ -0,0 +1,16 @@
T ( n -- ): select line n for editing.
P xxx(return): put typed line on selected line.

22
blk/102
View File

@ -1,14 +1,16 @@
103 LOAD 103 LOAD 104 LOAD
VARIABLE _K
: PGM : BROWSE
100 _LIST 100 _LIST
BEGIN BEGIN
KEY KEY CASE
DUP 'Q' = IF DROP EXIT THEN 'Q' OF DROP EXIT ENDOF
DUP 58 ( '9'+1 ) < IF _NUM 'B' OF B ENDOF
ELSE 'N' OF N ENDOF
_K ! _K (find) IF EXECUTE THEN _NUM
THEN ENDCASE
AGAIN AGAIN
; PGM ;

11
blk/103
View File

@ -5,5 +5,12 @@ VARIABLE ACC
IF _LIST 0 THEN IF _LIST 0 THEN
ACC ! ACC !
; ;
: B BLK> @ 1- DUP BLK> ! _LIST ; : L BLK> @ _LIST ;
: N BLK> @ 1+ DUP BLK> ! _LIST ; : B BLK> @ 1- BLK> ! L ;
: N BLK> @ 1+ BLK> ! L ;

16
blk/104 Normal file
View File

@ -0,0 +1,16 @@
( Line numbers for the user are 1-based, but in code, they're
0-based. )
VARIABLE EDPOS
: _bpos 64 * BLK( + ;
: T 1- DUP EDPOS ! _bpos (print) CRLF ;
: P
EDPOS @ _bpos C<
64 0 DO ( bpos c )
DUP 0xd = IF DROP 0 THEN
2DUP SWAP I + C!
DUP IF DROP C< THEN
LOOP
2DROP
BLK!!
;

View File

@ -16,6 +16,7 @@ SLATEST = ../tools/slatest
STRIPFC = ../tools/stripfc STRIPFC = ../tools/stripfc
BIN2C = ../tools/bin2c BIN2C = ../tools/bin2c
BLKPACK = ../tools/blkpack BLKPACK = ../tools/blkpack
BLKUNPACK = ../tools/blkunpack
.PHONY: all .PHONY: all
all: $(TARGETS) all: $(TARGETS)
@ -27,6 +28,7 @@ $(BLKPACK):
$(STRIPFC): $(BLKPACK) $(STRIPFC): $(BLKPACK)
$(SLATEST): $(BLKPACK) $(SLATEST): $(BLKPACK)
$(BIN2C): $(BLKPACK) $(BIN2C): $(BLKPACK)
$(BLKUNPACK): $(BLKPACK)
# z80c.bin is not in the prerequisites because it's a bootstrap # z80c.bin is not in the prerequisites because it's a bootstrap
# binary that should be updated manually through make updatebootstrap. # binary that should be updated manually through make updatebootstrap.
@ -77,6 +79,14 @@ emul.o: emul.c
updatebootstrap: forth/stage2 updatebootstrap: forth/stage2
cat $(BOOTSRCS) | ./forth/stage2 > ./forth/z80c.bin cat $(BOOTSRCS) | ./forth/stage2 > ./forth/z80c.bin
.PHONY: pack
pack:
rm blkfs && $(MAKE) blkfs
.PHONY: unpack
unpack:
$(BLKUNPACK) ../blk < blkfs
.PHONY: clean .PHONY: clean
clean: clean:
rm -f $(TARGETS) emul.o forth/*-bin.h forth/forth?.bin blkfs rm -f $(TARGETS) emul.o forth/*-bin.h forth/forth?.bin blkfs

View File

@ -12,8 +12,9 @@
// failing, send a non-zero value to RET_PORT to indicate failure // failing, send a non-zero value to RET_PORT to indicate failure
#define RET_PORT 0x01 #define RET_PORT 0x01
// Port for block reads. Write 2 bytes, MSB first, on that port and then // Port for block reads. Write 2 bytes, MSB first, on that port and then
// read 1024 bytes from the same port. // read 1024 bytes from the DATA port.
#define BLK_PORT 0x03 #define BLK_PORT 0x03
#define BLKDATA_PORT 0x04
static int running; static int running;
static FILE *fp; static FILE *fp;
@ -44,7 +45,16 @@ static void iowr_ret(uint8_t val)
retcode = val; retcode = val;
} }
static uint8_t iord_blk() static void iowr_blk(uint8_t val)
{
blkid <<= 8;
blkid |= val;
if (blkfp != NULL) {
fseek(blkfp, blkid*1024, SEEK_SET);
}
}
static uint8_t iord_blkdata()
{ {
uint8_t res = 0; uint8_t res = 0;
if (blkfp != NULL) { if (blkfp != NULL) {
@ -56,12 +66,10 @@ static uint8_t iord_blk()
return res; return res;
} }
static void iowr_blk(uint8_t val) static void iowr_blkdata(uint8_t val)
{ {
blkid <<= 8;
blkid |= val;
if (blkfp != NULL) { if (blkfp != NULL) {
fseek(blkfp, blkid*1024, SEEK_SET); putc(val, blkfp);
} }
} }
@ -99,8 +107,9 @@ int main(int argc, char *argv[])
m->iord[STDIO_PORT] = iord_stdio; m->iord[STDIO_PORT] = iord_stdio;
m->iowr[STDIO_PORT] = iowr_stdio; m->iowr[STDIO_PORT] = iowr_stdio;
m->iowr[RET_PORT] = iowr_ret; m->iowr[RET_PORT] = iowr_ret;
m->iord[BLK_PORT] = iord_blk;
m->iowr[BLK_PORT] = iowr_blk; m->iowr[BLK_PORT] = iowr_blk;
m->iord[BLKDATA_PORT] = iord_blkdata;
m->iowr[BLKDATA_PORT] = iowr_blkdata;
// initialize memory // initialize memory
for (int i=0; i<sizeof(KERNEL); i++) { for (int i=0; i<sizeof(KERNEL); i++) {
m->mem[i] = KERNEL[i]; m->mem[i] = KERNEL[i];

View File

@ -1,14 +1,22 @@
: EFS@ : EFS@
256 /MOD 3 PC! 3 PC! 256 /MOD 3 PC! 3 PC!
1024 0 DO 1024 0 DO
3 PC@ 4 PC@
BLK( I + C! BLK( I + C!
LOOP LOOP
; ;
: EFS!
256 /MOD 3 PC! 3 PC!
1024 0 DO
BLK( I + C@ 4 PC!
LOOP
;
: INIT : INIT
CURRENT @ HERE ! CURRENT @ HERE !
BLK$ BLK$
['] EFS@ BLK@* ! ['] EFS@ BLK@* !
['] EFS! BLK!* !
RDLN$ RDLN$
Z80A$ Z80A$
LIT< _sys [entry] LIT< _sys [entry]

View File

@ -7,23 +7,36 @@
: BLK!* 2 BLKMEM+ ; : BLK!* 2 BLKMEM+ ;
( Current blk pointer in ( ) ( Current blk pointer in ( )
: BLK> 4 BLKMEM+ ; : BLK> 4 BLKMEM+ ;
: BLK( 6 BLKMEM+ ; ( Whether buffer is dirty )
: BLKDTY 6 BLKMEM+ ;
: BLK( 8 BLKMEM+ ;
: BLK$ : BLK$
H@ 0x57 RAM+ ! H@ 0x57 RAM+ !
( 1024 for the block, 6 for variables ) ( 1024 for the block, 8 for variables )
1030 ALLOT 1032 ALLOT
( LOAD detects end of block with ASCII EOT. This is why ( LOAD detects end of block with ASCII EOT. This is why
we write it there. EOT == 0x04 ) we write it there. EOT == 0x04 )
4 C, 4 C,
0 BLKDTY !
-1 BLK> ! -1 BLK> !
; ;
( -- )
: BLK!
BLK> @ BLK!* @ EXECUTE
0 BLKDTY !
;
( n -- )
: BLK@ : BLK@
DUP BLK> = IF DROP EXIT THEN DUP BLK> @ = IF DROP EXIT THEN
BLKDTY @ IF BLK! THEN
DUP BLK> ! BLK@* @ EXECUTE DUP BLK> ! BLK@* @ EXECUTE
; ;
: BLK!! 1 BLKDTY ! ;
: .2 DUP 10 < IF SPC THEN . ; : .2 DUP 10 < IF SPC THEN . ;
: LIST : LIST
@ -38,7 +51,10 @@
: _ : _
(boot<) (boot<)
DUP 4 = IF DUP 4 = IF
DROP ( We drop our char, but also "a" from WORD: it won't
have the opportunity to balance PSP because we're
EXIT!ing. )
2DROP
( We're finished interpreting ) ( We're finished interpreting )
EXIT! EXIT!
THEN THEN

View File

@ -178,4 +178,8 @@
DUP ( I I ) DUP ( I I )
R> DROP I 2- @ ( I I a ) R> DROP I 2- @ ( I I a )
= UNTIL = UNTIL
DROP
; ;
( a -- a+1 c )
: C@+ DUP C@ SWAP 1+ SWAP ;

View File

@ -54,11 +54,10 @@
LOOP LOOP
DROP DROP
8 0 DO 8 0 DO
DUP C@ C@+
DUP <>{ 0x20 &< 0x7e |> <>} DUP <>{ 0x20 &< 0x7e |> <>}
IF DROP '.' THEN IF DROP '.' THEN
EMIT EMIT
1+
LOOP LOOP
CRLF CRLF
; ;

View File

@ -5,22 +5,21 @@
: (print) : (print)
BEGIN BEGIN
DUP C@ ( a c ) C@+ ( a+1 c )
( exit if null ) ( exit if null )
DUP NOT IF 2DROP EXIT THEN DUP NOT IF 2DROP EXIT THEN
EMIT ( a ) EMIT ( a )
1 + ( a+1 )
AGAIN AGAIN
; ;
: ." : ."
34 , ( 34 == litWord ) 34 , ( 34 == litWord )
BEGIN BEGIN
C< DUP ( c c ) C<
( 34 is ASCII for " ) ( 34 is ASCII for " )
DUP 34 = IF DROP DROP 0 0 THEN DUP 34 = IF DROP 0 THEN
C, DUP C,
0 = UNTIL NOT UNTIL
COMPILE (print) COMPILE (print)
; IMMEDIATE ; IMMEDIATE

View File

@ -1,7 +1,5 @@
: SLEN ( a -- n ) : SLEN ( a -- n )
DUP ( astart aend ) DUP ( astart aend )
BEGIN BEGIN C@+ NOT UNTIL
DUP C@ 0 = IF -^ EXIT THEN 1- -^
1+
AGAIN
; ;

1
tools/.gitignore vendored
View File

@ -9,3 +9,4 @@
/bin2c /bin2c
/exec /exec
/blkpack /blkpack
/blkunpack

View File

@ -9,9 +9,10 @@ STRIPFC_TGT = stripfc
BIN2C_TGT = bin2c BIN2C_TGT = bin2c
EXEC_TGT = exec EXEC_TGT = exec
BLKPACK_TGT = blkpack BLKPACK_TGT = blkpack
BLKUNPACK_TGT = blkunpack
TARGETS = $(MEMDUMP_TGT) $(BLKDUMP_TGT) $(UPLOAD_TGT) $(FONTCOMPILE_TGT) \ TARGETS = $(MEMDUMP_TGT) $(BLKDUMP_TGT) $(UPLOAD_TGT) $(FONTCOMPILE_TGT) \
$(TTYSAFE_TGT) $(PINGPONG_TGT) $(SLATEST_TGT) $(STRIPFC_TGT) \ $(TTYSAFE_TGT) $(PINGPONG_TGT) $(SLATEST_TGT) $(STRIPFC_TGT) \
$(BIN2C_TGT) $(EXEC_TGT) $(BLKPACK_TGT) $(BIN2C_TGT) $(EXEC_TGT) $(BLKPACK_TGT) $(BLKUNPACK_TGT)
OBJS = common.o OBJS = common.o
all: $(TARGETS) all: $(TARGETS)
@ -31,6 +32,7 @@ $(STRIPFC_TGT): $(STRIPFC_TGT).c
$(BIN2C_TGT): $(BIN2C_TGT).c $(BIN2C_TGT): $(BIN2C_TGT).c
$(EXEC_TGT): $(EXEC_TGT).c $(EXEC_TGT): $(EXEC_TGT).c
$(BLKPACK_TGT): $(BLKPACK_TGT).c $(BLKPACK_TGT): $(BLKPACK_TGT).c
$(BLKUNPACK_TGT): $(BLKUNPACK_TGT).c
$(TARGETS): $(OBJS) $(TARGETS): $(OBJS)
$(CC) $(CFLAGS) $@.c $(OBJS) -o $@ $(CC) $(CFLAGS) $@.c $(OBJS) -o $@

43
tools/blkunpack.c Normal file
View File

@ -0,0 +1,43 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
void usage()
{
fprintf(stderr, "Usage: blkunpack dirname\n");
}
int main(int argc, char *argv[])
{
char buf[1024];
int blkid = 0;
if (argc != 2) {
usage();
return 1;
}
while (fread(buf, 1024, 1, stdin) == 1) {
char fullpath[0x200];
sprintf(fullpath, "%s/%03d", argv[1], blkid);
char c = 0;
for (int i=0; i<1024; i++) {
c |= buf[i];
}
if (c) {
// not an empty block
FILE *fp = fopen(fullpath, "w");
for (int i=0; i<16; i++) {
int len = strlen(&buf[i*64]);
fwrite(&buf[i*64], len, 1, fp);
fputc('\n', fp);
}
fclose(fp);
} else {
// empty block, delete
unlink(fullpath);
}
blkid++;
}
return 0;
}

View File

@ -1,2 +0,0 @@
/cfspack
/cfsunpack

View File

@ -1,16 +0,0 @@
TARGETS = cfspack cfsunpack
CFSPACK_OBJS = cfspack.o libcfs.o
.PHONY: all
all: $(TARGETS)
cfspack: $(CFSPACK_OBJS)
$(CC) $(LDFLAGS) -o $@ $(CFSPACK_OBJS)
cfsunpack: cfsunpack.c
$(CC) $(CFLAGS) -o $@ cfsunpack.c
.PHONY: clean
clean:
rm -f $(TARGETS)

View File

@ -1,33 +0,0 @@
# cfspack
A tool/library to pack files into a CFS blob and unpack a CFS blob into
a directory.
## Usage
To pack a directory into a CFS blob, run:
cfspack /path/to/directory
The blob is spit to stdout. If there are subdirectories, they will be prefixes
to the filenames under it.
`cfspack` takes optional -p pattern arguments. If specified, only files
matching at least one of the patterns ("fnmatch" style") will be included.
If path is a file, a CFS with a single file will be spit and its name will
exclude the directory part of that filename.
The chain being spitted is always ended with a "stop block" (a zero-allocation
block that stops the CFS chain). You can call `cfspack` with no argument to get
only a stop block.
The program errors out if a file name is too long (> 26 bytes) or too big
(> 0x10000 - 0x20 bytes).
To unpack a blob to a directory:
cfsunpack /path/to/dest < blob
If destination exists, files are created alongside existing ones. If a file to
unpack already exists, it is overwritten.

View File

@ -1,10 +0,0 @@
#define BLKSIZE 0x100
#define HEADERSIZE 0x20
#define MAX_FN_LEN 25 // 26 - null char
#define MAX_FILE_SIZE (BLKSIZE * 0x100) - HEADERSIZE
void set_spit_stream(FILE *stream);
int is_regular_file(char *path);
void spitempty();
int spitblock(char *fullpath, char *fn);
int spitdir(char *path, char *prefix, char **patterns);

View File

@ -1,54 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <fnmatch.h>
#include <libgen.h>
#include <sys/stat.h>
#include "cfs.h"
void usage()
{
fprintf(stderr, "Usage: cfspack [-p pattern] [/path/to/dir...]\n");
}
int main(int argc, char *argv[])
{
int patterncount = 0;
char **patterns = malloc(sizeof(char**));
patterns[0] = NULL;
while (1) {
int c = getopt(argc, argv, "p:");
if (c < 0) {
break;
}
switch (c) {
case 'p':
patterns[patterncount] = optarg;
patterncount++;
patterns = realloc(patterns, sizeof(char**)*(patterncount+1));
patterns[patterncount] = NULL;
break;
default:
usage();
return 1;
}
}
int res = 0;
for (int i=optind; i<argc; i++) {
if (is_regular_file(argv[i])) {
// special case: just one file
res = spitblock(argv[i], basename(argv[i]));
} else {
res = spitdir(argv[i], "", patterns);
}
}
if (res == 0) {
spitempty();
}
free(patterns);
return res;
}

View File

@ -1,93 +0,0 @@
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <sys/stat.h>
#define BLKSIZE 0x100
#define HEADERSIZE 0x20
#define MAX_FN_LEN 25 // 26 - null char
bool ensuredir(char *path)
{
char *s = path;
while (*s != '\0') {
if (*s == '/') {
*s = '\0';
struct stat path_stat;
if (stat(path, &path_stat) != 0) {
if (mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) {
return false;
}
}
*s = '/';
}
s++;
}
return true;
}
bool unpackblk(char *dstpath)
{
char buf[MAX_FN_LEN+1];
if (fgets(buf, 3+1, stdin) == NULL) {
return false;
}
if (strcmp(buf, "CFS") != 0) {
return false;
}
int c = getchar();
uint8_t blkcnt = c;
if (blkcnt == 0) {
return false;
}
c = getchar();
uint16_t fsize = c & 0xff;
c = getchar();
fsize |= (c & 0xff) << 8;
if (fgets(buf, MAX_FN_LEN+1+1, stdin) == NULL) {
return false;
}
char fullpath[0x1000];
strcpy(fullpath, dstpath);
strcat(fullpath, "/");
strcat(fullpath, buf);
if (!ensuredir(fullpath)) {
return false;
}
int blksize = (BLKSIZE-HEADERSIZE)+(BLKSIZE*(blkcnt-1));
int skipcount = blksize - fsize;
FILE *fp = fopen(fullpath, "w");
while (fsize) {
c = getchar();
if (c == EOF) {
return false;
}
fputc(c, fp);
fsize--;
}
fclose(fp);
while (skipcount) {
getchar();
skipcount--;
}
return true;
}
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "Usage: cfspack /path/to/dest\n");
return 1;
}
char *dstpath = argv[1];
// we fail if there isn't at least one block
if (!unpackblk(dstpath)) {
return 1;
}
while (unpackblk(dstpath));
return 0;
}

View File

@ -1,152 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <fnmatch.h>
#include <libgen.h>
#include <sys/stat.h>
#include "cfs.h"
#define PUTC(c) putc(c, spitstream)
static FILE *spitstream = NULL;
void set_spit_stream(FILE *stream)
{
spitstream = stream;
}
int is_regular_file(char *path)
{
struct stat path_stat;
stat(path, &path_stat);
return S_ISREG(path_stat.st_mode);
}
void spitempty()
{
if (spitstream == NULL) spitstream = stdout;
PUTC('C');
PUTC('F');
PUTC('S');
for (int i=0; i<0x20-3; i++) {
PUTC(0);
}
}
int spitblock(char *fullpath, char *fn)
{
if (spitstream == NULL) spitstream = stdout;
FILE *fp = fopen(fullpath, "r");
fseek(fp, 0, SEEK_END);
long fsize = ftell(fp);
if (fsize > MAX_FILE_SIZE) {
fclose(fp);
fprintf(stderr, "File too big: %s %ld\n", fullpath, fsize);
return 1;
}
/* Compute block count.
* We always have at least one, which contains 0x100 bytes - 0x20, which is
* metadata. The rest of the blocks have a steady 0x100.
*/
unsigned char blockcount = 1;
int fsize2 = fsize - (BLKSIZE - HEADERSIZE);
if (fsize2 > 0) {
blockcount += (fsize2 / BLKSIZE);
}
if (blockcount * BLKSIZE < fsize + HEADERSIZE) {
blockcount++;
}
PUTC('C');
PUTC('F');
PUTC('S');
PUTC(blockcount);
// file size is little endian
PUTC(fsize & 0xff);
PUTC((fsize >> 8) & 0xff);
int fnlen = strlen(fn);
for (int i=0; i<MAX_FN_LEN; i++) {
if (i < fnlen) {
PUTC(fn[i]);
} else {
PUTC(0);
}
}
// And the last FN char which is always null
PUTC(0);
char buf[MAX_FILE_SIZE] = {0};
rewind(fp);
fread(buf, fsize, 1, fp);
fclose(fp);
fwrite(buf, (blockcount * BLKSIZE) - HEADERSIZE, 1, spitstream);
fflush(spitstream);
return 0;
}
int spitdir(char *path, char *prefix, char **patterns)
{
DIR *dp;
struct dirent *ep;
int prefixlen = strlen(prefix);
dp = opendir(path);
if (dp == NULL) {
fprintf(stderr, "Couldn't open directory.\n");
return 1;
}
while ((ep = readdir(dp))) {
if ((strcmp(ep->d_name, ".") == 0) || strcmp(ep->d_name, "..") == 0) {
continue;
}
if (ep->d_type != DT_DIR && ep->d_type != DT_REG) {
fprintf(stderr, "Only regular file or directories are supported\n");
return 1;
}
int slen = strlen(ep->d_name);
if (prefixlen + slen> MAX_FN_LEN) {
fprintf(stderr, "Filename too long: %s/%s\n", prefix, ep->d_name);
return 1;
}
char fullpath[0x1000];
strcpy(fullpath, path);
strcat(fullpath, "/");
strcat(fullpath, ep->d_name);
char newprefix[MAX_FN_LEN];
strcpy(newprefix, prefix);
if (prefixlen > 0) {
strcat(newprefix, "/");
}
strcat(newprefix, ep->d_name);
if (ep->d_type == DT_DIR) {
int r = spitdir(fullpath, newprefix, patterns);
if (r != 0) {
return r;
}
} else {
char **p = patterns;
// if we have no pattern, we match all
if (p && *p) {
int matches = 0;
while (*p) {
if (fnmatch(*p, ep->d_name, 0) == 0) {
matches = 1;
break;
}
p++;
}
if (!matches) {
continue;
}
}
int r = spitblock(fullpath, newprefix);
if (r != 0) {
return r;
}
}
}
closedir(dp);
return 0;
}