This is the improved version of NLSAscii.cmd
Additions/Changes:
- It now relaunches itself to properly run under the correct code page.
- Integrity verification code added to ASCII generator function so it can now reliably determine if the correct table has been produced.
- Greatly improved the performance of generator by some small changes. On my system the execution time reduced from around 0.35 seconds without integrity verification to around 0.12 seconds with integrity verification and 0.06 seconds without integrity check.
Please note this not a full batch application,it is only a demo to show how to handle the NLS files and extract a complete ASCII table from them. However incorporating the code for your own use is straightforward.
Code: Select all
:: NLSAscii.cmd
:: NLS ASCII TABLE Generator. By sst
:: Special thanks to carlos for his valuable feedback and guidances
:: Sample batch script to demonstrate creation of in-memory ASCII table using NLS files.
:: Dependency:
:: C_950.NLS SHA1: 1cd3f1ccf03d2b2d00dd3a133a6bebaa0e1bdb89
:: C_1252.NLS SHA1: 355e2ada0b9ea4f2a844c2d236d1b48336881b22
:: This script should produce the correct table on Win2K, WinXP, WinVista, Win7, Win8.x, Win10
:: Tested on Win2K SP4, WinXP SP3 32bit, Win7 SP1 64bit, Win10 1709 64bit
@echo off
setlocal EnableExtensions DisableDelayedExpansion
:: Using a unique enough name for init variable to effectively reduce the chance of unintentional collisions to zero.
set "Init=NLSAscii.Init.{10355d30-4e90-11e8-9330-aa819362ab44}"
if not defined %Init% (
%= Relaunching in new cmd instance to fully apply new code page. =%
setlocal EnableDelayedExpansion
set "errorlevel="
set "chcp=chcp"
>nul 2>&1 !chcp!
if errorlevel 1 (
set "errorlevel=!errorlevel!
echo,
<nul set /p "=WARNING: "
if "!errorlevel!"=="9009" (echo CHCP not found.) else (echo CHCP Error.)
echo Correct operation of program depends on the current console code page.
set "chcp=REM,"
set "errorlevel="
)
for /f "tokens=*" %%a in ('!chcp!') do for %%b in (%%a) do set "PreviousCodePage=%%~nb"
>nul !chcp! 437
set "%Init%=1"
"%COMSPEC%" /a /d /c "%~f0"
set "errorlevel=!errorlevel!
>nul 2>&1 !chcp! !PreviousCodePage! || echo WARNING: Could not revert back to the initial code page !PreviousCodePage!
exit /b !errorlevel!
)
set "%Init%="
set "Init="
set "TIME="
:: Relaunched in new cmd instance, actual code begins here
:: Set this to none zero to enable creation of one-byte .chr files.
set /a "EnableChrExport=1"
set "ChrSubDir=.\CHR_NLS"
echo,
<nul set /p "=Generating and verfying ASCII table ... "
set "Start=%TIME%"
call :CreateAsciiTableFromNLS ASCII_TABLE ErrMsg
if errorlevel 1 (
echo Failed.
(echo,%ErrMsg%)>&2
echo,
if /i %errorlevel% EQU 0x7FFFFFFF (
echo Integrity verfication failed.
echo This can happen for the following reasons:
echo - The batch file is running with an inappropriate code page,
echo The default working code page should be 437
echo - The NLS files [C_1252.NLS/C_950.NLS] may either be corrupt,
echo or have different binary structure than what is expected.
echo,
)
pause
exit /b %errorlevel%
)
set "End=%TIME%"
call :timediff Elapsed Start End
echo OK. Time taken: %Elapsed%
setlocal EnableDelayedExpansion
<nul>"ASCII_TABLE.dat" set /p "=!ASCII_TABLE!"
echo,
echo Ascii table has been created in ASCII_TABLE environment variable.
echo,
echo For manual verification of the generated table, The contents of ASCII_TABLE variable
echo have been written to the file "ASCII_TABLE.dat" in the current directory.
echo SHA1 hash sum of the file should be: 8760d3807fb0c8ce8fd426f4cf72632edf8789f6
echo,
set "ASCII_TABLE=#!ASCII_TABLE!"
set "LF=!ASCII_TABLE:~10,1!"
set "CR=!ASCII_TABLE:~13,1!"
set "SUB=!ASCII_TABLE:~26,1!"
if /i %EnableChrExport% NEQ 0 (
<nul set /p "=Do you want to additionally create one-byte .chr files?[YN] "
set "Key="
for /F "delims=" %%K in ('"xcopy ?^| /w 2>nul"') do if not defined Key set "Key=%%K"
if /i "!Key:~-1!"=="Y" (set "Key=Y") else (set "Key=N")
echo !Key!
if "!Key!"=="Y" (
<nul set /p "=Please Wait...!CR!"
if not exist "%ChrSubDir%\" MD "%ChrSubDir%"
set "DEC=%ChrSubDir%\DEC"
if not exist "!DEC!\" MD "!DEC!"
for /L %%i in (1,1,255) do (
(echo !ASCII_TABLE:~%%i,1!%SUB%)>"!DEC!\%%i.tmp"
copy /y "!DEC!\%%i.tmp" /a "!DEC!\%%i.chr" /b >nul
)
set "prompt=%SUB%"
"%COMSPEC%" /a /d /k<nul>"!DEC!\26.chr"
"%COMSPEC%" /u /d /k<nul>"!DEC!\0.tmp"
"%COMSPEC%" /a /d /k<nul>>"!DEC!\0.tmp"
type "!DEC!\0.tmp"|(pause>nul&findstr "^")>"!DEC!\0.tmp"
copy /y "!DEC!\0.tmp" /a "!DEC!\0.chr" /b >nul
del /f /q "!DEC!\*.tmp" >nul 2>&1
set "HEX=%ChrSubDir%\HEX"
if not exist "!HEX!\" MD "!HEX!"
set "HD=0 1 2 3 4 5 6 7 8 9 A B C D E F"
set "i=0"
for %%H in (!HD!) do for %%L in (!HD!) do (
copy /y "!DEC!\!i!.chr" "!HEX!\%%H%%L.chr" >nul
set /a "i+=1"
)
echo Done. !LF!
)
)
endlocal
pause
exit /b 0
:CreateAsciiTableFromNLS <outResult> [outErrorMessage]
setlocal EnableDelayedExpansion
set "NLS950=%SystemRoot%\system32\C_950.NLS"
set "NLS1252=%SystemRoot%\system32\C_1252.NLS"
if "%~1"=="" exit /b 2
if not exist "%NLS950%" (endlocal & 2>nul set "%~2=%0 : "%NLS950%" does not exist." & exit /b 1)
if not exist "%NLS1252%" (endlocal & 2>nul set "%~2=%0 : "%NLS1252%" does not exist." & exit /b 1)
:: Detection for Win2K is just for speed of reading file content
:: On Win2K, "SET /P" will read 1024 bytes of input buffer
:: On WinXP and later "SET /P" will read 1023 bytes of input buffer.
:: We need to know this to be able to seek to the correct offset.
:: Otherwise we have to use exsclusively "pause>nul" to seek the input file
:: which is much slower for larger offsets
:: PS Note:
:: There is a difference between using SET /P inside a pipe eg: TYPE "BinaryFile" | SET /P "CAPTURE="
:: and using SET /P to directly read file contents eg: (SET /P "CAPTURE=")<"BinaryFile"
:: In pipe mode "SET /P" will always eat the first 1023 bytes of input (1024 bytes on Win2K), no matter what the content is
:: and the result will be truncated at <NULL>, <CRLF> and <LFCR>
:: In direct read mode, SET /P will eat the whole 1023 or 1024 bytes ONLY if doesn't encounter one of the <CRLF> or <LFCR> pairs in the way,
:: in that case, SET /P will immediately stop reading from input upon reaching <CRLF> or <LFCR>
:: In both modes, all of the control characters(range 00-1F) which came immediately before <CRLF> or <LFCR> will be removed from the target variable.
:: Here, using the direct read mode to fast seek the NLS files is OK becuase there is no <CRLF> or <LFCR> in them.
:: But for the perpose of FastSeeking arbitrary files, only pipe mode can be used.
for /F "tokens=3" %%V in ('ver') do (
if "%%V"=="2000" (set "FastSeekBytes=1024") else (set "FastSeekBytes=1023")
)
set "@{FastSeek}=SET /P "=""
:: Win2K's cmd has a bug in which it expands variable values to zero in SET /A expressions,
:: if they are placed in the right side of the operands. Thus we have to expand them ourselves.
set /a "NLS950_SingleSeekBytes1=1782-%FastSeekBytes%"
set /a "NLS950_SingleSeekBytes2=1890-%FastSeekBytes%"
set /a "NLS1252_SingleSeekBytes=547"
:: Capture phase
(
for /L %%P in (1,1,%NLS1252_SingleSeekBytes%) do pause
set /p "CAPTURE="
set "CHAR_01-7F=!CAPTURE:~0,127!"
set "CHAR_A0-FF=!CAPTURE:~159,96!"
set "CHAR_EF=!CHAR_A0-FF:~79,1!"
)<"%NLS1252%">nul
(
%@{FastSeek}%
for /L %%P in (1,1,%NLS950_SingleSeekBytes1%) do pause
REM Equivalent to: for /L %%P in (1,1,1782) do pause>nul
set /p "CAPTURE="
set "CHAR_80-93=!CAPTURE:%CHAR_EF%=!
set "CHAR_80-93=!CHAR_80-93:~0,20!
)<"%NLS950%">nul
(
%@{FastSeek}%
for /L %%P in (1,1,%NLS950_SingleSeekBytes2%) do pause
REM Equivalent to: for /L %%P in (1,1,1890) do pause>nul
set /p "CAPTURE="
set "CHAR_94-9F=!CAPTURE:%CHAR_EF%=!
set "CHAR_94-9F=!CHAR_94-9F:~0,12!
)<"%NLS950%">nul
:: Verification phase
set "CAPTURE="
set "TIME="
set "RANDOM="
set /a "_ErrLevel=0x7FFFFFFF"
set "CBool=^!^!"
set "BasePath=."
if defined Temp if exist "%Temp%\" set "BasePath=%Temp%"
for /F "tokens=1,3 delims=0123456789" %%A in ("%TIME: =0%") do set "time.delims=%%A%%B"
for /F "tokens=1-4 delims=%time.delims%" %%A in ("%TIME: =0%") do set "unq=%%A%%B%%C%%D"
set "unq=TMPNLSASCVRFY-%unq:~0,8%-%RANDOM%
set "Verify.File=%BasePath%\_asciitable_%unq%.tmp"
set "Dumper.File=%BasePath%\_dumper_%unq%.tmp"
<nul>"%Verify.File%" set /p "=!CHAR_01-7F!!CHAR_80-93!!CHAR_94-9F!!CHAR_A0-FF!"
set "CHAR_01-7F="&set "CHAR_80-93="&set "CHAR_94-9F="&set "CHAR_A0-FF="&set "unq="
set "a32=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
set "Dumper.Data=%a32%%a32%%a32%#%a32%%a32%%a32%%a32%%a32:~2%" =96xa + 1x# + 158xa=255
for %%F in ("%Verify.File%") do if "%%~zF"=="255" (
<nul>"%Dumper.File%" set /p "=%Dumper.Data%"
set "a32="&set "Dumper.Data="
set /a "index=cmp=0"
for /F "skip=1 tokens=2" %%i in ('fc /b "%Verify.File%" "%Dumper.File%"') do (
if "!cmp!"=="0" (
set /a "index+=1, cmp=index-0x%%i" 2>nul || set "cmp=1"
)
)
set /a "_ErrLevel*=!CBool!(cmp|(255-index))"
)
(
endlocal
if /i %_ErrLevel% EQU 0 (
<"%Verify.File%">nul set /p "%~1="
) else (
2>nul set "%~2=%0 : ASCII table error, Integrity verfication failed."
)
del /f "%Verify.File%", "%Dumper.File%" >nul 2>&1
exit /b %_ErrLevel%
)
:: timediff
:: -> Input and output format is compatible with %TIME%
:: -> The time and decimal seperators can be different between StartTime and EndTime
:: -> Output will have the same time and decimal seperators as returned by %TIME% at runtime.
:: -> If EndTime is less than StartTime then:
:: EndTime will be treated as a time in the next day
:: in that case, function measures time difference between a maximum distance of 24 hours minus 1 centisecond
:: time elements can have values greater than their standard maximum value ex: 12:247:853.5214
:: provided than the total represented time does not exceed 24*360000 centiseconds (24:00:00.00)
:: otherwise the result will not be meaningful.
:: -> If EndTime is greater than or equals to StartTime then:
:: No formal limitation applies to the value of elements,
:: except that total represented time can not exceed 2147483647 centiseconds.
:: -> To normalize (convert) none-standard time values pass 0:0:0.0 as StartTime
:timediff <outDiff> <inStartTime> <inEndTime>
(
setlocal EnableDelayedExpansion
set "TIME=" & set "Input=!%~2!#!%~3!"
for /F "tokens=1,3,4,5,7,9,11 delims=0123456789 " %%A in ("!Input!#!TIME!") do (
set "user.time.delims=%%A%%B%%C%%D%%E " & set "sys.time.delims=%%F%%G"
)
)
for /F "tokens=1-8 delims=%user.time.delims%" %%a in ("%Input%") do (
for %%A in ("@h1=%%a" "@m1=%%b" "@s1=%%c" "@c1=%%d" "@h2=%%e" "@m2=%%f" "@s2=%%g" "@c2=%%h") do (
for /F "tokens=1,2 delims==" %%A in ("%%~A") do (
for /F "tokens=* delims=0" %%B in ("%%B") do set "%%A=%%B"
)
)
set /a "@d=(@h2-@h1)*360000+(@m2-@m1)*6000+(@s2-@s1)*100+(@c2-@c1), @sign=(@d>>31)&1, @d+=(@sign*24*360000), @h=(@d/360000), @d%%=360000, @m=@d/6000, @d%%=6000, @s=@d/100, @c=@d%%100"
)
(
if /i %@h% LEQ 9 set "@h=0%@h%"
if /i %@m% LEQ 9 set "@m=0%@m%"
if /i %@s% LEQ 9 set "@s=0%@s%"
if /i %@c% LEQ 9 set "@c=0%@c%"
)
(
endlocal
set "%~1=%@h%%sys.time.delims:~0,1%%@m%%sys.time.delims:~0,1%%@s%%sys.time.delims:~1,1%%@c%"
exit /b
)