mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-02 08:30:55 +11:00
Compare commits
5 Commits
5067d40e3b
...
9edab10a3a
Author | SHA1 | Date | |
---|---|---|---|
|
9edab10a3a | ||
|
79ce88c12c | ||
|
5cb4a7de9a | ||
|
eefa8e6de5 | ||
|
57e20f0532 |
2
blk/000
2
blk/000
@ -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
|
||||
each. You refer to blocks by numbers. You show them with LIST.
|
||||
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
|
||||
block, it means that the next block continues the same kind of
|
||||
|
14
blk/001
14
blk/001
@ -1,4 +1,16 @@
|
||||
MASTER INDEX
|
||||
|
||||
3 Usage 30 Dictionary
|
||||
70 Implementation notes 100 Block explorer
|
||||
70 Implementation notes 100 Block editor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
4
blk/003
4
blk/003
@ -10,3 +10,7 @@ Contents
|
||||
4 DOES> 6 Compilation vs meta-comp.
|
||||
8 I/O 11 Chained comparisons
|
||||
14 Addressed devices 18 Signed-ness
|
||||
|
||||
|
||||
|
||||
|
||||
|
1
blk/004
1
blk/004
@ -13,3 +13,4 @@ At compile time, colon definition stops processing words when
|
||||
reaching the DOES>.
|
||||
|
||||
Example: ": CONSTANT CREATE HERE @ ! DOES> @ ;"
|
||||
|
||||
|
2
blk/009
2
blk/009
@ -12,3 +12,5 @@ new "(parse)" word.
|
||||
|
||||
This way, we have a full-featured (and extensible) parsing with
|
||||
a tiny native core.
|
||||
|
||||
|
||||
|
12
blk/016
12
blk/016
@ -2,3 +2,15 @@
|
||||
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
|
||||
those words.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
7
blk/018
7
blk/018
@ -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.
|
||||
To compare whether something is negative, use the "<0" word
|
||||
which is the equivalent to "0x7fff >".
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
5
blk/030
5
blk/030
@ -9,3 +9,8 @@ Be sure to read usage guide (B3) first.
|
||||
52 Addressed devices 54 Arithmetic / Bits
|
||||
56 Logic 58 Strings
|
||||
60 I/O 64 Disk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
6
blk/032
6
blk/032
@ -8,3 +8,9 @@ Words between "()" are "support words" that aren't really meant
|
||||
to be used directly, but as part of another word.
|
||||
|
||||
"*I*" in description indicates an IMMEDIATE word.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
13
blk/035
13
blk/035
@ -1,3 +1,16 @@
|
||||
(cont.)
|
||||
~ - Container for native code. Usually not an executable word.
|
||||
? - Is it ...? (example: IMMED?)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
10
blk/038
10
blk/038
@ -4,3 +4,13 @@ FORGET x -- Rewind the dictionary (both CURRENT and HERE)
|
||||
PREV a -- a Return a wordref's previous entry.
|
||||
WHLEN a -- n Get word header length from wordref. That is,
|
||||
name length + 3. a is a wordref
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
1
blk/043
1
blk/043
@ -13,3 +13,4 @@ INTERPRET -- Get a line from stdin, compile it in tmp memory,
|
||||
then execute the compiled contents.
|
||||
QUIT -- Return to interpreter prompt immediately
|
||||
EXIT! -- Exit current INTERPRET loop.
|
||||
|
||||
|
5
blk/046
5
blk/046
@ -9,3 +9,8 @@ SWAP a b -- b a
|
||||
2DUP a b -- a b a b
|
||||
2OVER a b c d -- a b c d a b
|
||||
2SWAP a b c d -- c d a b
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
9
blk/048
9
blk/048
@ -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 second item to PS
|
||||
J -- n Copy RS third item to PS
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
1
blk/050
1
blk/050
@ -5,6 +5,7 @@ Memory
|
||||
? a -- Print value of addr a
|
||||
+! n a -- Increase value of addr a by n
|
||||
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
|
||||
CURRENT -- a Set a to wordref of last added entry.
|
||||
CURRENT* -- a A pointer to active CURRENT*. Useful
|
||||
|
6
blk/052
6
blk/052
@ -8,3 +8,9 @@ A! c a -- Indirect C!
|
||||
A@* -- a Address for A@ word
|
||||
A!* -- a Address for A! word
|
||||
AMOVE src dst u -- Same as MOVE, but with A@ and A!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
2
blk/054
2
blk/054
@ -12,3 +12,5 @@ OR a b -- c a | b -> c
|
||||
XOR a b -- c a ^ b -> c
|
||||
|
||||
Shortcuts: 1+ 2+ 1- 2-
|
||||
|
||||
|
||||
|
8
blk/056
8
blk/056
@ -6,3 +6,11 @@ Logic
|
||||
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.
|
||||
NOT f -- f Push the logical opposite of f
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
6
blk/058
6
blk/058
@ -8,3 +8,9 @@ LITS a -- Write word at addr a as a atring literal.
|
||||
S= a1 a2 -- f Returns whether string a1 == a2.
|
||||
SCPY a -- Copy string at addr a into HERE.
|
||||
SLEN a -- n Push length of str at a.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
10
blk/064
10
blk/064
@ -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
|
||||
form of 16 lines of 64 columns.
|
||||
LOAD n -- Interprets Forth code from block n
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
10
blk/070
10
blk/070
@ -4,3 +4,13 @@ Implementation notes
|
||||
75 Stack management 77 Dictionary
|
||||
80 System variables 85 Word routines
|
||||
89 Initialization sequence
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
5
blk/071
5
blk/071
@ -9,3 +9,8 @@ it. As a general rule, we go like this:
|
||||
4. Is it a number?
|
||||
5. If yes, push that number to PS, goto 1
|
||||
6. Error: undefined word.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
2
blk/075
2
blk/075
@ -12,3 +12,5 @@ IX always points to RS' Top Of Stack (TOS)
|
||||
This return stack contain "Interpreter pointers", that is a
|
||||
pointer to the address of a word, as seen in a compiled list of
|
||||
words.
|
||||
|
||||
|
||||
|
4
blk/078
4
blk/078
@ -10,3 +10,7 @@ chain). There are also "special words", for example NUMBER,
|
||||
LIT, FBR, that have a slightly different structure. They're
|
||||
also a pointer to an executable, but as for the other fields,
|
||||
the only one they have is the "flags" field.
|
||||
|
||||
|
||||
|
||||
|
||||
|
10
blk/084
10
blk/084
@ -4,3 +4,13 @@ DRIVERS section is reserved for recipe-specific
|
||||
drivers. Here is a list of known usages:
|
||||
|
||||
* 0x70-0x78: ACIA buffer pointers in RC2014 recipes.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
10
blk/087
10
blk/087
@ -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 is pushed on the PSP and IP is advanced
|
||||
to the address following the null.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
9
blk/091
9
blk/091
@ -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
|
||||
comments. This can lead to peculiar code in this area where we
|
||||
try to "waste" space in initialization code.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
18
blk/100
18
blk/100
@ -1,10 +1,16 @@
|
||||
Block explorer
|
||||
Block editor
|
||||
|
||||
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
|
||||
explorer interpreter. Typing "Q" quits the program.
|
||||
Browse mode: If you execute BROWSE, the Forth interpreter is
|
||||
replaced by browser's loop. Typing "Q" quits the browser.
|
||||
|
||||
Typing a decimal number followed by space or return lists the
|
||||
contents of that block. B for previous block, N for next.
|
||||
In this mode, typing a decimal number followed by space or
|
||||
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
16
blk/101
Normal file
@ -0,0 +1,16 @@
|
||||
T ( n -- ): select line n for editing.
|
||||
P xxx(return): put typed line on selected line.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
22
blk/102
22
blk/102
@ -1,14 +1,16 @@
|
||||
103 LOAD
|
||||
VARIABLE _K
|
||||
103 LOAD 104 LOAD
|
||||
|
||||
: PGM
|
||||
: BROWSE
|
||||
100 _LIST
|
||||
BEGIN
|
||||
KEY
|
||||
DUP 'Q' = IF DROP EXIT THEN
|
||||
DUP 58 ( '9'+1 ) < IF _NUM
|
||||
ELSE
|
||||
_K ! _K (find) IF EXECUTE THEN
|
||||
THEN
|
||||
KEY CASE
|
||||
'Q' OF DROP EXIT ENDOF
|
||||
'B' OF B ENDOF
|
||||
'N' OF N ENDOF
|
||||
_NUM
|
||||
ENDCASE
|
||||
AGAIN
|
||||
; PGM
|
||||
;
|
||||
|
||||
|
||||
|
||||
|
11
blk/103
11
blk/103
@ -5,5 +5,12 @@ VARIABLE ACC
|
||||
IF _LIST 0 THEN
|
||||
ACC !
|
||||
;
|
||||
: B BLK> @ 1- DUP BLK> ! _LIST ;
|
||||
: N BLK> @ 1+ DUP BLK> ! _LIST ;
|
||||
: L BLK> @ _LIST ;
|
||||
: B BLK> @ 1- BLK> ! L ;
|
||||
: N BLK> @ 1+ BLK> ! L ;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
16
blk/104
Normal file
16
blk/104
Normal 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!!
|
||||
;
|
||||
|
@ -16,6 +16,7 @@ SLATEST = ../tools/slatest
|
||||
STRIPFC = ../tools/stripfc
|
||||
BIN2C = ../tools/bin2c
|
||||
BLKPACK = ../tools/blkpack
|
||||
BLKUNPACK = ../tools/blkunpack
|
||||
|
||||
.PHONY: all
|
||||
all: $(TARGETS)
|
||||
@ -27,6 +28,7 @@ $(BLKPACK):
|
||||
$(STRIPFC): $(BLKPACK)
|
||||
$(SLATEST): $(BLKPACK)
|
||||
$(BIN2C): $(BLKPACK)
|
||||
$(BLKUNPACK): $(BLKPACK)
|
||||
|
||||
# z80c.bin is not in the prerequisites because it's a bootstrap
|
||||
# binary that should be updated manually through make updatebootstrap.
|
||||
@ -77,6 +79,14 @@ emul.o: emul.c
|
||||
updatebootstrap: forth/stage2
|
||||
cat $(BOOTSRCS) | ./forth/stage2 > ./forth/z80c.bin
|
||||
|
||||
.PHONY: pack
|
||||
pack:
|
||||
rm blkfs && $(MAKE) blkfs
|
||||
|
||||
.PHONY: unpack
|
||||
unpack:
|
||||
$(BLKUNPACK) ../blk < blkfs
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f $(TARGETS) emul.o forth/*-bin.h forth/forth?.bin blkfs
|
||||
|
@ -12,8 +12,9 @@
|
||||
// failing, send a non-zero value to RET_PORT to indicate failure
|
||||
#define RET_PORT 0x01
|
||||
// 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 BLKDATA_PORT 0x04
|
||||
|
||||
static int running;
|
||||
static FILE *fp;
|
||||
@ -44,7 +45,16 @@ static void iowr_ret(uint8_t 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;
|
||||
if (blkfp != NULL) {
|
||||
@ -56,12 +66,10 @@ static uint8_t iord_blk()
|
||||
return res;
|
||||
}
|
||||
|
||||
static void iowr_blk(uint8_t val)
|
||||
static void iowr_blkdata(uint8_t val)
|
||||
{
|
||||
blkid <<= 8;
|
||||
blkid |= val;
|
||||
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->iowr[STDIO_PORT] = iowr_stdio;
|
||||
m->iowr[RET_PORT] = iowr_ret;
|
||||
m->iord[BLK_PORT] = iord_blk;
|
||||
m->iowr[BLK_PORT] = iowr_blk;
|
||||
m->iord[BLKDATA_PORT] = iord_blkdata;
|
||||
m->iowr[BLKDATA_PORT] = iowr_blkdata;
|
||||
// initialize memory
|
||||
for (int i=0; i<sizeof(KERNEL); i++) {
|
||||
m->mem[i] = KERNEL[i];
|
||||
|
@ -1,14 +1,22 @@
|
||||
: EFS@
|
||||
256 /MOD 3 PC! 3 PC!
|
||||
1024 0 DO
|
||||
3 PC@
|
||||
4 PC@
|
||||
BLK( I + C!
|
||||
LOOP
|
||||
;
|
||||
: EFS!
|
||||
256 /MOD 3 PC! 3 PC!
|
||||
1024 0 DO
|
||||
BLK( I + C@ 4 PC!
|
||||
LOOP
|
||||
;
|
||||
|
||||
: INIT
|
||||
CURRENT @ HERE !
|
||||
BLK$
|
||||
['] EFS@ BLK@* !
|
||||
['] EFS! BLK!* !
|
||||
RDLN$
|
||||
Z80A$
|
||||
LIT< _sys [entry]
|
||||
|
26
forth/blk.fs
26
forth/blk.fs
@ -7,23 +7,36 @@
|
||||
: BLK!* 2 BLKMEM+ ;
|
||||
( Current blk pointer in ( )
|
||||
: BLK> 4 BLKMEM+ ;
|
||||
: BLK( 6 BLKMEM+ ;
|
||||
( Whether buffer is dirty )
|
||||
: BLKDTY 6 BLKMEM+ ;
|
||||
: BLK( 8 BLKMEM+ ;
|
||||
|
||||
: BLK$
|
||||
H@ 0x57 RAM+ !
|
||||
( 1024 for the block, 6 for variables )
|
||||
1030 ALLOT
|
||||
( 1024 for the block, 8 for variables )
|
||||
1032 ALLOT
|
||||
( LOAD detects end of block with ASCII EOT. This is why
|
||||
we write it there. EOT == 0x04 )
|
||||
4 C,
|
||||
0 BLKDTY !
|
||||
-1 BLK> !
|
||||
;
|
||||
|
||||
( -- )
|
||||
: BLK!
|
||||
BLK> @ BLK!* @ EXECUTE
|
||||
0 BLKDTY !
|
||||
;
|
||||
|
||||
( n -- )
|
||||
: BLK@
|
||||
DUP BLK> = IF DROP EXIT THEN
|
||||
DUP BLK> @ = IF DROP EXIT THEN
|
||||
BLKDTY @ IF BLK! THEN
|
||||
DUP BLK> ! BLK@* @ EXECUTE
|
||||
;
|
||||
|
||||
: BLK!! 1 BLKDTY ! ;
|
||||
|
||||
: .2 DUP 10 < IF SPC THEN . ;
|
||||
|
||||
: LIST
|
||||
@ -38,7 +51,10 @@
|
||||
: _
|
||||
(boot<)
|
||||
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 )
|
||||
EXIT!
|
||||
THEN
|
||||
|
@ -178,4 +178,8 @@
|
||||
DUP ( I I )
|
||||
R> DROP I 2- @ ( I I a )
|
||||
= UNTIL
|
||||
DROP
|
||||
;
|
||||
|
||||
( a -- a+1 c )
|
||||
: C@+ DUP C@ SWAP 1+ SWAP ;
|
||||
|
@ -54,11 +54,10 @@
|
||||
LOOP
|
||||
DROP
|
||||
8 0 DO
|
||||
DUP C@
|
||||
C@+
|
||||
DUP <>{ 0x20 &< 0x7e |> <>}
|
||||
IF DROP '.' THEN
|
||||
EMIT
|
||||
1+
|
||||
LOOP
|
||||
CRLF
|
||||
;
|
||||
|
@ -5,22 +5,21 @@
|
||||
|
||||
: (print)
|
||||
BEGIN
|
||||
DUP C@ ( a c )
|
||||
C@+ ( a+1 c )
|
||||
( exit if null )
|
||||
DUP NOT IF 2DROP EXIT THEN
|
||||
EMIT ( a )
|
||||
1 + ( a+1 )
|
||||
AGAIN
|
||||
;
|
||||
|
||||
: ."
|
||||
34 , ( 34 == litWord )
|
||||
BEGIN
|
||||
C< DUP ( c c )
|
||||
C<
|
||||
( 34 is ASCII for " )
|
||||
DUP 34 = IF DROP DROP 0 0 THEN
|
||||
C,
|
||||
0 = UNTIL
|
||||
DUP 34 = IF DROP 0 THEN
|
||||
DUP C,
|
||||
NOT UNTIL
|
||||
COMPILE (print)
|
||||
; IMMEDIATE
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
: SLEN ( a -- n )
|
||||
DUP ( astart aend )
|
||||
BEGIN
|
||||
DUP C@ 0 = IF -^ EXIT THEN
|
||||
1+
|
||||
AGAIN
|
||||
BEGIN C@+ NOT UNTIL
|
||||
1- -^
|
||||
;
|
||||
|
1
tools/.gitignore
vendored
1
tools/.gitignore
vendored
@ -9,3 +9,4 @@
|
||||
/bin2c
|
||||
/exec
|
||||
/blkpack
|
||||
/blkunpack
|
||||
|
@ -9,9 +9,10 @@ STRIPFC_TGT = stripfc
|
||||
BIN2C_TGT = bin2c
|
||||
EXEC_TGT = exec
|
||||
BLKPACK_TGT = blkpack
|
||||
BLKUNPACK_TGT = blkunpack
|
||||
TARGETS = $(MEMDUMP_TGT) $(BLKDUMP_TGT) $(UPLOAD_TGT) $(FONTCOMPILE_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
|
||||
|
||||
all: $(TARGETS)
|
||||
@ -31,6 +32,7 @@ $(STRIPFC_TGT): $(STRIPFC_TGT).c
|
||||
$(BIN2C_TGT): $(BIN2C_TGT).c
|
||||
$(EXEC_TGT): $(EXEC_TGT).c
|
||||
$(BLKPACK_TGT): $(BLKPACK_TGT).c
|
||||
$(BLKUNPACK_TGT): $(BLKUNPACK_TGT).c
|
||||
$(TARGETS): $(OBJS)
|
||||
$(CC) $(CFLAGS) $@.c $(OBJS) -o $@
|
||||
|
||||
|
43
tools/blkunpack.c
Normal file
43
tools/blkunpack.c
Normal 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;
|
||||
}
|
2
tools/cfspack/.gitignore
vendored
2
tools/cfspack/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/cfspack
|
||||
/cfsunpack
|
@ -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)
|
@ -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.
|
@ -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);
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user