diff --git a/README.md b/README.md index 598ede9..f2fb6ea 100644 --- a/README.md +++ b/README.md @@ -172,3 +172,7 @@ You may use this under the terms of the GNU General Public License GPL v3 or the * ...everyone who contributed to **3dbrew.org** Copyright (C) 2021 derrek, profi200, d0k3 + + +Known Issue: +盗版卡存档有问题 diff --git a/include/arm11/acf.h b/include/arm11/acf.h index 4280090..f6e921a 100644 --- a/include/arm11/acf.h +++ b/include/arm11/acf.h @@ -6,24 +6,64 @@ #include #include -#define ACFONT_NOT_FOUND 1 -#define ACFONT_INVALID 2 -#define ACFONT_READ_EOF 3 -#define ACFONT_NOT_SUPPORT 4 -#define ACFONT_MEM_EMPTY 5 +#ifdef __cplusplus +extern "C" { +#endif -extern int acf_set_font(const char *); -extern const char *acf_draw(int, int, unsigned, unsigned, unsigned, uint16_t, const char *); +#define INPUT(type) type +#define OUTPUT(type) type* -#define ACF_LIT_POINT(x, y, w, h, islit) \ - { \ - if (0 <= (x) && (x) < (w) && 0 <= (y) && (y) < (h)) \ - { \ - if ((islit)) \ - { \ - frame[ (h)*(x) + (h-1-y) ] = fg; \ - } \ - } \ - } +// error type +#define ACFONT_NOT_FOUND -1 +#define ACFONT_INVALID -2 +#define ACFONT_READ_EOF -3 +#define ACFONT_NOT_SUPPORT -4 +#define ACFONT_MEM_EMPTY -5 + +typedef int acf_error_t; +typedef void * acf_canvas_t; +typedef void * acf_callerdata_t; +typedef uint16_t acf_position_t; +typedef uint16_t acf_rectedge_t; +typedef uint8_t acf_color_t; +typedef const char * acf_text_t; +typedef uint16_t acf_counter_t; + +typedef void (*acf_visitor_t)( + INPUT(acf_callerdata_t) some_data_provide_by_caller, + INPUT(acf_position_t) position_x_of_canvas, + INPUT(acf_position_t) position_y_of_canvas, + INPUT(acf_color_t) color_in_position_xy_of_canvas +); + +extern acf_error_t acf_initialize( + INPUT(acf_text_t) filename_of_acf_font +); + +extern acf_canvas_t acf_get_canvas( + INPUT(acf_rectedge_t) width_in_pixel_of_canvas, + INPUT(acf_text_t) text_painting_in_utf8, + + OUTPUT(acf_rectedge_t) using_width_in_pixel_of_canvas, + OUTPUT(acf_counter_t) rendered_char_counting, + OUTPUT(acf_text_t) text_not_rendered_in_utf8 +); + +extern void acf_recycle( + INPUT(acf_canvas_t) canvas_should_free; +); + +extern acf_canvas_t acf_use_canvas( + INPUT(acf_canvas_t) canvas_should_use, + INPUT(acf_visitor_t) pixel_visitor_for_canvas, + INPUT(acf_callerdata_t) some_data_from_caller +); + +#undef INPUT +#undef OUTPUT + +#ifdef __cplusplus +} +#endif #endif //_ANOD_COMPILED_FONT_H_ diff --git a/libn3ds/include/arm11/drivers/gx.h b/libn3ds/include/arm11/drivers/gx.h index 895552d..de3bee3 100644 --- a/libn3ds/include/arm11/drivers/gx.h +++ b/libn3ds/include/arm11/drivers/gx.h @@ -23,7 +23,7 @@ -#define GX_REGS_BASE (IO_MEM_ARM11_ONLY + 0x200000) +#define GX_REGS_BASE (IO_MEM_ARM11_ONLY + 0x200000) // 10400000H #define REG_GX_GPU_CLK *((vu32*)(GX_REGS_BASE + 0x0004)) // ? // PSC (memory fill) regs. diff --git a/open_agb_firm.firm b/open_agb_firm.firm index 8c54565..1778fd8 100644 Binary files a/open_agb_firm.firm and b/open_agb_firm.firm differ diff --git a/source/arm11/acf.c b/source/arm11/acf.c index bd4a817..4994099 100644 --- a/source/arm11/acf.c +++ b/source/arm11/acf.c @@ -10,13 +10,6 @@ #include "arm11/fmt.h" #include "fs.h" -// 画点函数 -#ifndef ACF_LIT_POINT -#error "ACF_LIT_POINT SHOULD BE DECLARE BEFORE USING GB2312 FONT" -#endif - -extern uint8_t canvas[]; - typedef struct { uint16_t start; @@ -27,8 +20,8 @@ typedef struct typedef struct { - uint8_t width; - uint8_t height; + acf_rectedge_t width; + acf_rectedge_t height; int8_t offsetx; int8_t offsety; char *filename; @@ -59,7 +52,7 @@ static ACFont gblfont = { // 2 使用该字体在一个原点范围内渲染一行文字 // 设置字体名称 -int acf_set_font(const char *acfile) +acf_error_t acf_initialize(acf_text_t acfile) { // open the font file FHandle font; @@ -241,7 +234,7 @@ static inline int readS6(uint8_t *p, int index) } #define PIX_IN_LINE(x,y,w) (x) + (y)*(w) -static int render_unicode(FHandle fd, int *x, unsigned width, unsigned height, uint32_t code, unsigned *width_max, uint8_t *ram) +static int render_unicode(FHandle fd, int *x, unsigned width, unsigned height, uint32_t code, uint8_t *ram) { uint32_t readed; @@ -287,11 +280,11 @@ static int render_unicode(FHandle fd, int *x, unsigned width, unsigned height, u // render // 从上往下(y从大到小,x从小到大)进行绘制 - int px = *x, py = 0-gblfont.offsety; // px/py不参与计算位置 + int px = *x, py = -1-gblfont.offsety; // px/py不参与计算位置 int cx = px + bbx[2], cy = py + bbx[1] + bbx[3]; // cx/cy计算位置进行绘制 // 先检查宽度 - if ((width_max != NULL) && (cx + bbx[0] >= (int)(*width_max))) + if ( cx + bbx[0] >= (int)width ) return 1; // 绘制 @@ -299,11 +292,12 @@ static int render_unicode(FHandle fd, int *x, unsigned width, unsigned height, u { for (int j = 0; j < bbx[0]; ++j) { - if( 0 <= i && i < (int)height && 0 <= j && j < (int)width ) + int tx = cx+j, ty = cy-i; + if( 0 <= ty && ty < (int)height && 0 <= tx && tx < (int)width ) { if( BIT_AT_POS(glyph, bbx[0] * i + j) ) { - SET_AT_POS( ram, PIX_IN_LINE(cx+j, cy-i, width) ); + SET_AT_POS( ram, PIX_IN_LINE(tx, ty, width) ); } } } @@ -314,62 +308,78 @@ static int render_unicode(FHandle fd, int *x, unsigned width, unsigned height, u return 0; } +#define ACFONT_CANVAS_MEMSIZE( w, h ) ( ( (w)*(h) + BIT_PER_BYTE-1 )/BIT_PER_BYTE+sizeof(acf_rectedge_t) ) +#define ACFONT_CANVAS_WIDTH( p ) (*(acf_rectedge_t*)(p)) +#define ACFONT_CANVAS_PIXEL( p ) ((uint8_t*)(p)+sizeof(acf_rectedge_t)) + +void acf_recycle( acf_canvas_t canvas ) +{ + if( canvas != NULL ) + free(canvas); +} + +acf_canvas_t acf_use_canvas(acf_canvas_t canvas, acf_visitor_t pixel_responser, acf_callerdata_t data) +{ + if( canvas == NULL ) return canvas; + + register acf_rectedge_t width = ACFONT_CANVAS_WIDTH(canvas), height = gblfont.height; + for( acf_position_t i=0; i < height; i++ ) + { + acf_position_t dy = height - 1 - i; + for( register acf_position_t j=0; j < width; j++ ){ + uint8_t m = BIT_AT_POS(ACFONT_CANVAS_PIXEL(canvas), PIX_IN_LINE(j, dy, width)); + pixel_responser(data, j, i, m>0 ? 1 : 0); + } + } + return canvas; +} + // 根据字体绘制中文字符 // x,y - 绘制一行字符的基准点 // maxwidth - 最长绘制多少个像素点,填0则忽略此参数 // utf8_line - utf8字符串 // 返回:第一个未绘制的字符的位置,如果width为0,则返回永远是NULL -const char *acf_draw(int x, int y, unsigned width, unsigned height, unsigned maxwidth, uint16_t color, const char *utf8_line) +acf_canvas_t acf_get_canvas(acf_rectedge_t width, acf_text_t text, acf_rectedge_t *realwidth, acf_counter_t *renderedcnt, acf_text_t *rest) { FHandle font; - int linex = 0; if( gblfont.filename == NULL || RES_OK != fOpen(&font, gblfont.filename, FA_OPEN_EXISTING | FA_READ) ) { // log - return utf8_line; + return NULL; } uint32_t unicode; - if( maxwidth == 0 ) maxwidth = width; - unsigned *option = width == 0 ? NULL : &maxwidth; + int linex = 0; + int rendered_count = 0; + const char *utf8_line = text; - const int ramsize = gblfont.height * maxwidth + BIT_PER_BYTE - 1 / BIT_PER_BYTE; - uint8_t *localram = malloc( ramsize ); - memset( localram, 0, ramsize ); + acf_rectedge_t *canvas = malloc( ACFONT_CANVAS_MEMSIZE(width, gblfont.height) ); + memset(canvas, 0, ACFONT_CANVAS_MEMSIZE(width, gblfont.height)); + *canvas = width; for (const char *next = next_unicode(utf8_line, &unicode); next != NULL; next = next_unicode(utf8_line, &unicode)) { - int error = render_unicode(font, &linex, maxwidth, height, unicode, option, localram); - if (error > 0 ) break; - else if(error < 0 ) error = render_unicode(font, &linex, maxwidth, height, '?', option, localram); + int error = render_unicode(font, &linex, width, gblfont.height, unicode, ACFONT_CANVAS_PIXEL(canvas)); + if( error > 0 ) break; + else if( error < 0 ) error = render_unicode(font, &linex, width, gblfont.height, '?', ACFONT_CANVAS_PIXEL(canvas)); if( error ) { - free( localram ); + free( canvas ); fClose(font); - return utf8_line; + return NULL; } utf8_line = next; + ++rendered_count; } - // copy back to canvas - uint16_t *frame = consoleGet()->frameBuffer; - uint16_t fg = color; - for( int i=0; i < gblfont.height; ++i ) - { - for( int j=0; j < (int)maxwidth; ++j ) - { - if( BIT_AT_POS(localram, PIX_IN_LINE(j, i, maxwidth)) ) - { - ACF_LIT_POINT(x+j, y-(gblfont.height-1-i), (int)width, (int)height, 1); - } - } - } - - free( localram ); + if( realwidth != NULL ) *realwidth = linex; + if( renderedcnt != NULL ) *renderedcnt = rendered_count; + if( rest != NULL ) *rest = utf8_line; + fClose(font); - return NULL; + return canvas; } diff --git a/source/arm11/filebrowser.c b/source/arm11/filebrowser.c index aa10ddd..b51d5e0 100644 --- a/source/arm11/filebrowser.c +++ b/source/arm11/filebrowser.c @@ -58,6 +58,38 @@ typedef struct char *ptrs[MAX_DIR_ENTRIES]; // For fast sorting. } DirList; +void set_screen_color( acf_callerdata_t data, acf_position_t tx, acf_position_t ty, acf_color_t b ) +{ + if( b == 0 ) return; + + int *draw_data = data; + u16 *frame = consoleGet()->frameBuffer; + + int x = tx + draw_data[0]; + int y = ty + draw_data[1]; + if( 0 <= x && x < draw_data[2] && 0 <= y && y < draw_data[3] ){ + frame[ x*draw_data[3] + (draw_data[3]-1-y) ] = (u16)draw_data[4]; + } +} + +const char *acf_draw(int x, int y, int width, int height, int maxwidth, u16 color, const char* text) +{ + uint8_t draw_data[sizeof(int)*5]; + + int *draw_data_int = (int*)&draw_data; + draw_data_int[0] = x; + draw_data_int[1] = y; + draw_data_int[2] = width; + draw_data_int[3] = height; + draw_data_int[4] = color; + + const char *retval = text; + acf_canvas_t canvas = acf_get_canvas(maxwidth, text, NULL, NULL, &retval); + if( !canvas ) return retval; + acf_recycle( acf_use_canvas(canvas, set_screen_color, draw_data) ); + return retval; +} + int dlistCompare(const void *a, const void *b) { const char *entA = *(char**)a; @@ -171,7 +203,7 @@ Result browseFiles(const char *const basePath, char selected[512]) if( dList->num > 0 ){ //ee_printf(*dList->ptrs[oldCursorPos] == ENT_TYPE_FILE ? "\x1b[%lu;H\x1b[37m %.51s" : "\x1b[%lu;H\x1b[36m %.51s", oldCursorPos - windowPos, &dList->ptrs[oldCursorPos][1]); // Clear old cursor. //ee_printf("\x1b[%lu;H\x1b[33m>%.51s", cursorPos - windowPos, &dList->ptrs[cursorPos][1]); // Draw cursor. - if( oldCursorPos != cursorPos && windowPos <= oldCursorPos && oldCursorPos < windowPos + SCREEN_ROWS ) + if( oldCursorPos != cursorPos && windowPos <= (u32)oldCursorPos && (u32)oldCursorPos < windowPos + SCREEN_ROWS ) { const uint8_t fg = *dList -> ptrs[oldCursorPos] == ENT_TYPE_FILE ? 7:6; acf_draw( CLEFTMARGIN, LINENO_TO_Y(oldCursorPos-windowPos), CWIDTH, CHEIGHT, CLINELIMIT, consoleGetRGB565Color(fg), &dList->ptrs[oldCursorPos][1] ); diff --git a/source/arm11/open_agb_firm.c b/source/arm11/open_agb_firm.c index e57c2c5..1b3d226 100644 --- a/source/arm11/open_agb_firm.c +++ b/source/arm11/open_agb_firm.c @@ -526,6 +526,8 @@ static void gbaGfxHandler(void *args) } GX_processCommandList(listSize, list); GFX_waitForP3D(); + // 地址0x18180000保存的是360x240大小的贴图数据 + // 地址0x18200000保存的是512x512大小的GBA的240x160贴图数据 GX_displayTransfer((u32*)(0x18180000 + (16 * 240 * 3)), 368u<<16 | 240u, GFX_getFramebuffer(SCREEN_TOP) + (16 * 240 * 3), 368u<<16 | 240u, 1u<<12 | 1u<<8); GFX_waitForPPF(); @@ -684,7 +686,7 @@ Result oafParseConfigEarly(void) // Create the saves folder. if((res = fMkdir(OAF_SAVE_DIR)) != RES_OK && res != RES_FR_EXIST) break; - if((res = acf_set_font("wqy11.fnt")) != RES_OK ) break; + if((res = acf_initialize("wqy11.fnt")) != RES_OK ) break; // Parse the config. res = parseOafConfig("config.ini", true); @@ -743,7 +745,7 @@ Result oafInitAndRun(void) // Initialize the legacy frame buffer and frame handler. const KHandle frameReadyEvent = createEvent(false); - LGYFB_init(frameReadyEvent, g_oafConfig.scaler); // Setup Legacy Framebuffer. + LGYFB_init(frameReadyEvent, g_oafConfig.scaler); // 这里把GBA的输出转换成0x18200000处512x512大小的纹理 patchGbaGpuCmdList(g_oafConfig.scaler); createTask(0x800, 3, gbaGfxHandler, (void*)frameReadyEvent); g_frameReadyEvent = frameReadyEvent; diff --git a/tools/acf-builder/buildacf.js b/tools/acf-builder/buildacf.js new file mode 100644 index 0000000..27bb602 --- /dev/null +++ b/tools/acf-builder/buildacf.js @@ -0,0 +1,310 @@ +const {open} = require("fs/promises"); +const {argv} = require("process"); + +// ACF文件格式: +// 0-3:ACFv ACF为固定字符串,v表示版本号,从1开始 +// 4-7:font bounding 来自bdf文件 +// 8-9: fragment items size 字体数据开始的位置 +// 10-:fragment items 分段描述数据,一个分段的unicode是连续的 +// +// 分段描述数据内容 +// 0-1:start unicode 本分段第一个unicode +// 2-3:end unicode 本分段最后一个unicode +// 5: padding size for unicode 本分段每个unicode的字体数据占用的字节长度 +// 6: bbxd count in this fragment 本分段总共用到的bbxd种类 +// other: 4*(bbxd count) 本分段用到的所有bbxd的原始数据 +// +// bbxd数据格式: +// 4个6字节的整数(-32 ~ 31),表示BBX对应的内容,加上一个6字节的整数表示dwidth的第一个数据 +// +// 字体数据 +// 同一个fragment里面的所有unicode长度都是padding size,如果实际数据少的会补齐0 +// 如果fragment的bbxd count为1,则字体数据就是直接boundingbox的所有点阵数据 +// 如果fragment的bbxd count大于1,则字体数据是1字节的bbxd索引,后接boundingbox的所有点阵数据 + +const helper = () => { + const setBitAt = (bitarr, pos) => { + let byteIndex = Math.floor( pos / 8 ); + let bitOffset = pos % 8; + bitarr[ byteIndex ] |= 1 << bitOffset; + } + const testBit = (byte, pos) => byte & ( 1 << pos); + const putS6 = (arr, val, pos) => { + // sign bit + if( val < 0 ) { + setBitAt( arr, pos ); + val = -val; + } + ++pos; + + // digits + for( let i=1<<4; i > 0; i>>=1, pos++ ){ + if( val & i ) { + setBitAt(arr, pos); + } + } + } + return {setBitAt, testBit, putS6} +} + +const createDescript = () => { + let numChars, boundingbox; + let glyphs = [] + let charGlyph = {} + let fragments = [] + + const apply = async ( cmd, params ) => { + if( cmd == "chars" ) { + [numChars] = params.map( n => Number(n) ); + } + else if( cmd == "fontboundingbox" ) { + boundingbox = params.map( n => Number(n) ); + } + else if( cmd == "startchar" ){ + charGlyph = {}; + + if( fragments.length == 0 ){ + fragments.push({ padding: 0, bbxd: new Map() }); + } + } + else if( cmd == "encoding" ){ + charGlyph.code = Number(params[0]); + } + else if( cmd == "dwidth" ){ + charGlyph.dwidth = params.map( n => Number(n) ); + } + else if( cmd == "bbx" ){ + charGlyph.bbx = params.map( n => Number(n) ); + } + else if( cmd == "bitmap" ){ + charGlyph.recoding = true; + charGlyph.data = []; + } + else if( cmd == "endchar" ){ + const {recoding, code, ...value} = charGlyph; + const [w, h] = charGlyph.bbx; + + let fragment = fragments.at(-1); + if( !("start" in fragment) ){ + fragment.start = code; + } + else if( glyphs.at(-1).code + 1 != code ){// break + fragment.end = glyphs.at(-1).code; + + // new fragment + fragment = { start: code, padding: 0, bbxd: new Map() }; + fragments.push( fragment ); + } + + if( w*h > fragment.padding ){ + fragment.padding = w*h; + } + + const key = JSON.stringify( [...value.bbx, value.dwidth[0]] ) + if( !fragment.bbxd.has(key) ){ + fragment.bbxd.set( key, fragment.bbxd.size ); + } + + charGlyph = {}; + glyphs.push( {...value, code, fragment} ); + } + else if( cmd == "endfont" ){ + const fragment = fragments.at(-1); + fragment.end = glyphs.at(-1).code; + } + else if( charGlyph.recoding ){ + charGlyph.data.push( parseInt(cmd, 16) ); + } + } + const save = async file => { + const { setBitAt, testBit, putS6 } = helper(); + const packHead = () => { + const buffer = new ArrayBuffer(6); + const view = new DataView(buffer); + view.setUint16(0, numChars, true); + boundingbox.forEach( (c, i) => view.setInt8(2+i, c) ) + return new Uint8Array(buffer); + } + const packHeadV2 = fragSize => { + const buffer = new ArrayBuffer(10); + const view = new DataView(buffer); + view.setUint8(0, "A".charCodeAt()) + view.setUint8(1, "C".charCodeAt()) + view.setUint8(2, "F".charCodeAt()) + view.setUint8(3, 2)//v2 + boundingbox.forEach( (c, i) => view.setInt8(4+i, c) ) + view.setUint16(8, fragSize, true) + return new Uint8Array(buffer); + } + const packGlyphs = () => { + return glyphs.map( glyph => [packChar(glyph), packPixel(glyph), glyph] ) + } + const packChar = glyph => { + const buffer = new Uint8Array(4); + const bitwid = 6; + glyph.bbx.forEach( (c,i) => putS6(buffer, c, bitwid*i) ); + putS6( buffer, glyph.dwidth[0], bitwid*4 ); + return buffer; + } + const packPixel = glyph => { + const [w, h] = glyph.bbx; + const {padding} = glyph.fragment; + const nbyte = Math.ceil( padding / 8 ); + const buffer = new Uint8Array( nbyte ); + const base = 8 * ( (7+w) >> 3 ) - 1; + for( let j = 0; j < h; ++j ){ + let linepixel = glyph.data[j]; + for( let i = 0; i < w; ++i ){ + if( testBit(linepixel, base-i) ){ + setBitAt( buffer, j*w+i ) + } + } + } + return buffer; + } + const packIndex = fonts => { + const buffer = new ArrayBuffer( numChars * 5 ); + const view = new DataView( buffer ); + let offset = 0; + glyphs.forEach( (c, i) => { + view.setUint16( i*5, c.code, true ); + view.setUint8( i*5 + 2, offset >> 16 ); + view.setUint8( i*5 + 3, offset & 0xFF ); + view.setUint8( i*5 + 4, (offset & 0xFFFF) >> 8 ); + offset += 4 + fonts[i][1].length; + }) + return new Uint8Array(buffer); + } + const packFragments = () => { + const bitwid = 6; + const serials = fragments.map( frag => { + const buffer = new ArrayBuffer( 6+4*frag.bbxd.size ); + const view = new DataView( buffer ); + const nbyte = Math.ceil( frag.padding / 8 ); + view.setUint16( 0, frag.start, true ); + view.setUint16( 2, frag.end, true ); + view.setUint8( 4, frag.bbxd.size > 1 ? 1+nbyte : nbyte ); + view.setUint8( 5, frag.bbxd.size ); + + frag.bbxd.forEach( (idx, key) => { + const arr = new Uint8Array(buffer, 6+idx*4, 4); + const embeded = JSON.parse( key ); + embeded.forEach( (c,i)=>putS6(arr, c, bitwid*i) ) + } ) + return new Uint8Array(buffer); + }) + return serials; + } + + /* + * old code: pack for acfv1 + const head = packHead(); + const glyph = packGlyphs(); + const index = packIndex( glyph ); + + let out = await open(file, "w"); + await out.write(head); + await out.write(index); + let jobs = Promise.resolve(); + glyph.forEach( ([char, pixel]) => { + const combine = Uint8Array.from([...char, ...pixel]) + jobs = jobs.then( () => out.write(combine) ) + } ) + await jobs; + await out.close(); + */ + + const index = packFragments(); + const head = packHeadV2( index.reduce( (r,b)=>r+b.length, 0 ) ); + const glyph = packGlyphs(); + + let out = await open(file, "w"); + await out.write( head ); + let jobs = Promise.resolve(); + index.forEach( buffer => { + jobs = jobs.then( () => out.write(buffer) ) + } ); + await jobs; + + jobs = Promise.resolve(); + glyph.forEach( ([char, pixel, glyph]) => { + const {fragment} = glyph; + if( fragment.bbxd.size == 1 ) jobs = jobs.then( () => out.write(pixel) ) + else { + const key = JSON.stringify( [...glyph.bbx, glyph.dwidth[0]] ) + const combine = Uint8Array.from( [fragment.bbxd.get(key), ...pixel] ) + jobs = jobs.then( () => out.write(combine) ) + } + } ) + await jobs; + await out.close(); + } + return { apply, save } +} + +const createReader = file => { + const newline = "\n".charCodeAt(); + + let buffer = new Uint8Array(400); + let cursor = buffer.length; + let line = "" + const readline = async () => { + if( cursor == buffer.length ){ + cursor = 0; + await file.read( buffer, cursor, buffer.length ) + } + + let chars = [] + let fullline = null; + while( cursor < buffer.length ){ + const char = buffer[cursor++]; + if( char == newline ) { + fullline = `${line}${String.fromCharCode(...chars)}`; + break; + } + + chars.push( char ) + } + + if( fullline ){ + line = ""; // reset line + return fullline.length > 0 ? fullline.split(" ").map( (word,i) => i==0 ? word.toLowerCase():word ) : [null]; + } + else { + line = `${line}${String.fromCharCode(...chars)}`; + return await readline() + } + } + return readline; +} + +const convert = async fname => { + let bdf = await open( fname, "r" ); + + const readline = createReader( bdf ); + const bdfdesc = createDescript(); + + while( true ){ + let [cmd, ...params] = await readline(); + + await bdfdesc.apply( cmd, params ) + + if( cmd == "endfont" ) break; + } + + await bdf.close(); + await bdfdesc.save( fname.replace(".bdf", ".acf") ); +} + +const start = async params => { + if( params.length < 1 ){ + console.log("Usage: buildact file [file2] [...]") + return; + } + + const formatter = new Intl.DateTimeFormat("zh-CN", {dateStyle: "short", timeStyle: "medium", timeZone: "Asia/Shanghai"}) + globalThis.debug = log => console.log( `[DEBUG][${formatter.format(new Date())}]${log}` ) + await Promise.all( params.map( file => convert(file) ) ) +} + +start( argv.slice(2) ); \ No newline at end of file