::: 델파이 Tip&Trick :::

델파이 Tip&Trick 성격에 맞지 않는 광고,비방,질문의 글은 즉시 삭제하며
내용을 복사하여 사용할 경우 반드시 이곳(http://www.howto.pe.kr)을 출처로 명시하여 주세요


Category

  김영대(2003-03-07 21:59:01, Hit : 5153, Vote : 1203
 ROM-BIOS 정보 읽기

(*******************************************************************************
*                                                                              *
* BIOS Help - read ROM BIOS on Windows 95/98/SE/ME/NT/2K/XP                    *
*                                                                              *
* Copyright (C) 2001, Nico Bendlin (nico@bendlin.de)                           *
*                                                                              *
* Compiler: Delphi 4.03/5.01/6.00                                              *
* Version: 1.03, 2001-09-02                                                    *
*                                                                              *
*******************************************************************************)

{ postum scriptum: sorry for the bad english, i wrote it in a hurry }

unit BiosHelp;

{$ALIGN ON}
{$MINENUMSIZE 4}

interface

uses
  Windows;

type
  PRomBiosDump = ^TRomBiosDump;
  TRomBiosDump = array [$000F0000..$000FFFFF] of Byte;

type
  TReadRomBiosMethod = (
    rrbmAutomatic, { Autodetect OS type and use proper method }
    rrbmGeneric,   { Use 16-bit COM program to dump the BIOS  }
    rrbmMemory,    { Read from memory (Win9x)                 }
    rrbmPhysical   { Read from physical memory object (WinNT) }
  );

function ReadRomBios(var Dump: TRomBiosDump; Method: TReadRomBiosMethod;
  Timeout: DWORD = INFINITE): Boolean;

function GetRomBiosBuffer(const Dump: TRomBiosDump; Address: Pointer;
  var Buffer; BufferSize: Cardinal): Cardinal;
function GetRomBiosString(const Dump: TRomBiosDump; Address: Pointer): string;
function GetRomBiosLongLong(const Dump: TRomBiosDump; Address: Pointer): LONGLONG;
function GetRomBiosDWord(const Dump: TRomBiosDump; Address: Pointer): DWORD;
function GetRomBiosWord(const Dump: TRomBiosDump; Address: Pointer): Word;
function GetRomBiosByte(const Dump: TRomBiosDump; Address: Pointer): Byte;

implementation

{###############################################################################
#                                                                              #
#                             GENERIC METHOD                                   #
#                                                                              #
# Create an temporary folder, save an 16bit COM program (RomDump.com) into it, #
# execute program redirected to an file (Rom.dmp, RomDump.com simply dumps the #
# memory range F000:0000-F000:FFFF to STDOUT), read dump file into the buffer, #
# and finally cleanup all temporary files and directories.                     #
#                                                                              #
# (the function RomDumpCode is x86 specific, which i wrote to generate 16-bit  #
#  code with the help of the 23-bit Delphi compiler, never try to execute the  #
#  pseudo-code in your program! it will not work in 32-bit protected mode)     #
#                                                                              #
###############################################################################}

{ *INTERNAL* - Pseudo 16-bit code }

type
  PRomDumpCodeInfo = ^TRomDumpCodeInfo;
  TRomDumpCodeInfo = (rdciStart, rdciEnd, rdciSize);

function _RomDumpCode(Info: TRomDumpCodeInfo): Pointer;
var
  CodeStart: Pointer;
  CodeEnd: Pointer;
begin
  asm
          JMP     @@End

          { *BEGIN* 16-bit code  }
          { -- never use it in your program! -- }
          { COM which writes ROM-BIOS to StdOut }
  @@Start:
          { Dump F000:0000-F000:FFFE }
          XOR     eDX, eDX  // DS = 0xF000   ; Data segment
          MOV     DH, 0F0h
          MOV     DS, eDX
          XOR     eDX, eDX  // DX = 0x0000   ; Data offset
          XOR     eCX, eCX  // CX = 0xFFFF   ; Data length
          DEC     eCX
          XOR     eBX, eBX  // BX = 0x0001   ; STDOUT (file handle)
          INC     eBX
          MOV     AH, 40h   // DosCall(0x40) ; INT21, DOS_WRITE_TO_HANDLE
          INT     21h
          JC      @@Exit    // On error exit ; AL = Error code
          { Dump F000:FFFF }
          XOR     eDX, eDX  // DS = 0xF000   ; Data segment
          MOV     DH, 0F0h
          MOV     DS, eDX
          XOR     eDX, eDX  // DX = 0xFFFF   ; Data offset
          DEC     eDX
          XOR     eCX, eCX  // CX = 0x0001   ; Data length
          INC     eCX
          MOV     eBX, eCX  // BX = 0x0001   ; STDOUT (file handle)
          MOV     AH, 40h   // DosCall(0x40) ; INT21, DOS_WRITE_TO_HANDLE
          INT     21h
          JC      @@Exit    // On error exit ; AL = Error code
          MOV     AL, 0     // no error      ; AL = 0
  @@Exit:
          MOV     AH, 4Ch   // DosCall(0x4C) ; INT21, DOS_TERMINATE_EXE
          INT     21h
  @@End:
          { *END* 16-bit code  }

          MOV     CodeStart, OFFSET @@Start
          MOV     CodeEnd, OFFSET @@End
  end;
  case Info of
    rdciStart:
      Result := CodeStart;
    rdciEnd:
      Result := CodeEnd;
    rdciSize:
      Result := Pointer(Cardinal(CodeEnd) - Cardinal(CodeStart));
  else
    Result := nil;
  end;
end;

{ *INTERNAL* - Save 16-bit code to file }

function _RomDumpCodeToFile(const Filename: string): Boolean;
var
  ComFile: THandle;
  Size: Cardinal;
begin
  Result := False;
  ComFile := CreateFile(PChar(Filename), GENERIC_WRITE, FILE_SHARE_READ, nil,
    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
  if ComFile <> INVALID_HANDLE_VALUE then
  try
    Result := WriteFile(ComFile, _RomDumpCode(rdciStart)^,
      Cardinal(_RomDumpCode(rdciSize)), Size, nil) and
      (Size = Cardinal(_RomDumpCode(rdciSize)));
    if not Result then
      DeleteFile(PChar(Filename));
  finally
    CloseHandle(ComFile);
  end;
end;

{ *INTERNAL* - Execute 16-bit code redirected to file }

function _RomDumpCodeExecute(const Com, Dmp: string; Timeout: DWORD): Boolean;
var
  ComSpec: string;
  si: TStartupInfo;
  pi: TProcessInformation;
begin
  Result := False;
  SetLength(ComSpec, MAX_PATH);
  SetLength(ComSpec,
    GetEnvironmentVariable('ComSpec', PChar(@ComSpec[1]), MAX_PATH));
  if Length(ComSpec) > 0 then
  begin
    FillChar(si, SizeOf(TStartupInfo), 0);
    si.cb := SizeOf(TStartupInfo);
    si.dwFlags := STARTF_USESHOWWINDOW;
    si.wShowWindow := SW_HIDE;
    if CreateProcess(nil, PChar(ComSpec + ' /C ' + Com + ' > ' + Dmp),
      nil, nil, False, CREATE_NEW_CONSOLE or CREATE_NEW_PROCESS_GROUP, nil,
      nil, si, pi) then
    try
      Result := WaitForSingleObject(pi.hProcess, Timeout) <> WAIT_TIMEOUT;
    finally
      CloseHandle(pi.hProcess);
      CloseHandle(pi.hThread);
    end;
  end;
end;

function DirectoryExists(const Dir: string): Boolean;
var
  Attr: DWORD;
begin
  Attr := GetFileAttributes(PChar(Dir));
  Result := (Attr <> $FFFFFFFF) and
    (Attr and FILE_ATTRIBUTE_DIRECTORY = FILE_ATTRIBUTE_DIRECTORY);
end;

{ Get BIOS dump the generic way }

function ReadRomBios16(var Buffer: TRomBiosDump; Timeout: DWORD): Boolean;
const
  TempSub = '~RomDmp';
  ComName = 'RomDump.com';
  DmpName = 'Rom.dmp';
var
  TempPath: string;
  TempDir: string;
  TempIdx: Integer;
  TempIdxStr: string;
  ComFile: string;
  DmpFile: string;
  DmpHandle: THandle;
  Written: DWORD;
begin
  Result := False;
  SetLength(TempPath, MAX_PATH);
  SetLength(TempPath, GetTempPath(MAX_PATH, PChar(@TempPath[1])));
  if Length(TempPath) > 0 then
  begin
    if (TempPath[Length(TempPath)] <> '') then
      TempPath := TempPath + '';
    TempIdx := 0;
    repeat
      Inc(TempIdx);
      Str(TempIdx, TempIdxStr);
      TempDir := TempPath + TempSub + TempIdxStr;
    until not DirectoryExists(TempDir);
    if CreateDirectory(PChar(TempDir), nil) then
    try
      TempDir := TempDir + '';
      ComFile := TempDir + ComName;
      DmpFile := TempDir + DmpName;
      if _RomDumpCodeToFile(ComFile) then
      try
        if _RomDumpCodeExecute(ComFile, DmpFile, Timeout) then
        begin
          DmpHandle := CreateFile(PChar(DmpFile), GENERIC_READ,
            FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
          if DmpHandle <> INVALID_HANDLE_VALUE then
          try
            FillChar(Buffer, SizeOf(TRomBiosDump), 0);
            Result := ReadFile(DmpHandle, Buffer, SizeOf(TRomBiosDump),
              Written, nil) and (Written = SizeOf(TRomBiosDump));
          finally
            CloseHandle(DmpHandle);
          end;
        end;
      finally
        DeleteFile(PChar(DmpFile));
        DeleteFile(PChar(ComFile));
      end;
    finally
      RemoveDirectory(PChar(TempDir));
    end;
  end;
end;

{###############################################################################
#                                                                              #
#                           DIRECT METHOD (Win9x)                              #
#                                                                              #
# Due to the fact that Windows 95/98/ME maps the BIOS into every Win32 process #
# for read access it is very simple to fill the buffer from memory.            #
#                                                                              #
###############################################################################}

function ReadRomBios9x(var Buffer: TRomBiosDump): Boolean;
begin
  Result := False;
  try
    FillChar(Buffer, SizeOf(TRomBiosDump), 0);
    Move(Pointer(Low(TRomBiosDump))^, Buffer, SizeOf(TRomBiosDump));
    Result := True;
  except
    // ignore exceptions
  end
end;

{###############################################################################
#                                                                              #
#                       PHYSICAL MEMORY METHOD (WinNT)                         #
#                                                                              #
# On Windows NT the ROM BIOS is only available through the named kernel object #
# 'DevicePhysicalMemory'. Because it is impossible to open kernel objects in #
# user mode with standard Win32 API functions we make use of NT's nativeAPI in #
# NtDll.dll ("NT-Layer") namely ZwOpenSection.                                 #
#                                                                              #
# (note: mostly there are two versions of every function ZwXxx and NtXxx. The  #
#  only difference in kernel mode is that the NtXxx version works in conside-  #
#  ration to security while ZwXxx not. But in user mode both work like NtXxx.) #
#                                                                              #
# At first the section is opened with ZwOpenSection. Normally we would proceed #
# ZwMapViewOfSection, ZwUnmapViewOfSection, and NtClose. But the functions are #
# more complex and there is no needing for it. With the handle (because we are #
# in the "very simple" user mode =) we now use MapViewOfFile, UnmapViewOfFile, #
# and CloseHandle to map an memory window (the ROM BIOS) into our process.     #
#                                                                              #
# Due to the fact that ZwOpenSection returns NT error-codes in case of failure #
# we have to translate it to an Win32 error-code (RtlNtStatusToDosError).      #
# All NT specific functions are dynamically loaded -- because the applications #
# should start on Win9x systems =)                                             #
#                                                                              #
###############################################################################}

{ For more information see Windows 2000/XP DDK  }
{ It works on Windows NT 4.0 too, use NtDll.dll }

type
  NTSTATUS = Integer;

const
  STATUS_SUCCESS        = NTSTATUS(0);
  STATUS_INVALID_HANDLE = NTSTATUS($C0000008);
  STATUS_ACCESS_DENIED  = NTSTATUS($C0000022);

type
  PUnicodeString = ^TUnicodeString;
  TUnicodeString = packed record
    Length: Word;
    MaximumLength: Word;
    Buffer: PWideChar;
  end;

const
  OBJ_INHERIT          = $00000002;
  OBJ_PERMANENT        = $00000010;
  OBJ_EXCLUSIVE        = $00000020;
  OBJ_CASE_INSENSITIVE = $00000040;
  OBJ_OPENIF           = $00000080;
  OBJ_OPENLINK         = $00000100;
  OBJ_KERNEL_HANDLE    = $00000200;
  OBJ_VALID_ATTRIBUTES = $000003F2;

type
  PObjectAttributes = ^TObjectAttributes;
  TObjectAttributes = record
    Length: ULONG;
    RootDirectory: THandle;
    ObjectName: PUnicodeString;
    Attributes: ULONG;
    SecurityDescriptor: PSecurityDescriptor;
    SecurityQualityOfService: PSecurityQualityOfService;
  end;

const
  ObjectPhysicalMemoryDeviceName = 'DevicePhysicalMemory';
  ObjectPhysicalMemoryName: TUnicodeString = (
    Length: Length(ObjectPhysicalMemoryDeviceName) * 2;
    MaximumLength: Length(ObjectPhysicalMemoryDeviceName) * 2 + 2;
    Buffer: ObjectPhysicalMemoryDeviceName;
  );
  ObjectPhysicalMemoryAccessMask: ACCESS_MASK = SECTION_MAP_READ;
  ObjectPhysicalMemoryAttributes: TObjectAttributes =(
    Length: SizeOf(TObjectAttributes);
    RootDirectory: 0;
    ObjectName: @ObjectPhysicalMemoryName;
    Attributes: OBJ_CASE_INSENSITIVE;
    SecurityDescriptor: nil;
    SecurityQualityOfService: nil;
  );

type
  TFNZwOpenSection = function(out SectionHandle: THandle;
    DesiredAccess: ACCESS_MASK; ObjectAttributes: PObjectAttributes): NTSTATUS;
    stdcall;
  TFNRtlNtStatusToDosError = function(Status: NTSTATUS): DWORD; stdcall;

const
  ntdll = 'ntdll.dll';

var
  ZwOpenSection: TFNZwOpenSection;
  RtlNtStatusToDosError: TFNRtlNtStatusToDosError;

function ReadRomBiosNt(var Buffer: TRomBiosDump; Timeout: DWORD): Boolean;
var
  NtLayer: HMODULE;
  Status: NTSTATUS;
  Section: THandle;
  View: Pointer;
begin
  Result := False;
  NtLayer := GetModuleHandle(ntdll);
  if NtLayer = 0 then
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED)
  else
  begin
    if not Assigned(ZwOpenSection) then
      ZwOpenSection := GetProcAddress(NtLayer, 'ZwOpenSection');
    if not Assigned(RtlNtStatusToDosError) then
      RtlNtStatusToDosError := GetProcAddress(NtLayer, 'RtlNtStatusToDosError');
    if not (Assigned(ZwOpenSection) and Assigned(RtlNtStatusToDosError)) then
      SetLastError(ERROR_CALL_NOT_IMPLEMENTED)
    else
    begin
      Status := ZwOpenSection(Section, ObjectPhysicalMemoryAccessMask,
        @ObjectPhysicalMemoryAttributes);
      case Status of
        STATUS_SUCCESS:
          try
            View := MapViewOfFile(Section, ObjectPhysicalMemoryAccessMask, 0,
              Low(TRomBiosDump), SizeOf(TRomBiosDump));
            if Assigned(View) then
            try
              FillChar(Buffer, SizeOf(TRomBiosDump), 0);
              Move(View^, Buffer, SizeOf(TRomBiosDump));
              Result := True;
            finally
              UnmapViewOfFile(View);
            end;
          finally
            CloseHandle(Section);
          end;
        STATUS_ACCESS_DENIED:
          Result := ReadRomBios16(Buffer, Timeout);
      else
        SetLastError(RtlNtStatusToDosError(Status))
      end;
    end;
  end;
end;

{###############################################################################
#                                                                              #
#                               ReadRomBios                                    #
#                                                                              #
###############################################################################}

function ReadRomBios(var Dump: TRomBiosDump; Method: TReadRomBiosMethod;
  Timeout: DWORD = INFINITE): Boolean;
begin
  Result := False;
  case Method of
    rrbmAutomatic:
      if (Integer(GetVersion) < 0) then
      try
        Result := ReadRomBios9x(Dump);
      except
        Result := ReadRomBios16(Dump, Timeout);
      end
      else
        Result := ReadRomBiosNt(Dump, Timeout);
    rrbmGeneric:
      Result := ReadRomBios16(Dump, Timeout);
    rrbmMemory:
      Result := ReadRomBios9x(Dump);
    rrbmPhysical:
      Result := ReadRomBiosNt(Dump, Timeout);
  else
    SetLastError(ERROR_INVALID_PARAMETER);
  end;
end;

{###############################################################################
#                                                                              #
#     Utilities to simplify the access to data as generic standard types       #
#                                                                              #
###############################################################################}

function GetRomBiosBuffer(const Dump: TRomBiosDump; Address: Pointer;
  var Buffer; BufferSize: Cardinal): Cardinal;
begin
  Result := 0;
  if (Cardinal(Address) >= Low(TRomBiosDump)) and
    (Cardinal(Address) <= High(TRomBiosDump)) then
  begin
    Result := BufferSize;
    if (Cardinal(Address) + BufferSize > High(TRomBiosDump)) then
      Result := High(TRomBiosDump) - Cardinal(Address) + 1;
    Move(Dump[Cardinal(Address)], Buffer, Result);
  end;
end;

function GetRomBiosString(const Dump: TRomBiosDump; Address: Pointer): string;
begin
  Result := '';
  if (Cardinal(Address) >= Low(TRomBiosDump)) and
    (Cardinal(Address) <= High(TRomBiosDump)) then
    Result := string(PChar(@Dump[Cardinal(Address)]));
end;

function GetRomBiosLongLong(const Dump: TRomBiosDump; Address: Pointer): LONGLONG;
type
  PLongLong = ^LONGLONG;
begin
  Result := 0;
  if (Cardinal(Address) >= Low(TRomBiosDump)) and
    (Cardinal(Address) <= High(TRomBiosDump) - SizeOf(LONGLONG) + 1) then
    Result := PLongLong(@Dump[Cardinal(Address)])^;
end;

function GetRomBiosDWord(const Dump: TRomBiosDump; Address: Pointer): DWORD;
begin
  Result := 0;
  if (Cardinal(Address) >= Low(TRomBiosDump)) and
    (Cardinal(Address) <= High(TRomBiosDump) - SizeOf(DWORD) + 1) then
    Result := PDWORD(@Dump[Cardinal(Address)])^;
end;

function GetRomBiosWord(const Dump: TRomBiosDump; Address: Pointer): Word;
begin
  Result := 0;
  if (Cardinal(Address) >= Low(TRomBiosDump)) and
    (Cardinal(Address) <= High(TRomBiosDump) - SizeOf(Word) + 1) then
    Result := PWord(@Dump[Cardinal(Address)])^;
end;

function GetRomBiosByte(const Dump: TRomBiosDump; Address: Pointer): Byte;
begin
  Result := 0;
  if (Cardinal(Address) >= Low(TRomBiosDump)) and
    (Cardinal(Address) <= High(TRomBiosDump) - SizeOf(Byte) + 1) then
    Result := PByte(@Dump[Cardinal(Address)])^;
end;

end.





491   [네트웍/인터넷] 네트워크 컴퓨터가 존재하는지 검사하기  김영대 2003/03/07 4779 1208
490   [데이터베이스] DBGrid 에서 수직 Scroll Bar 감추기  김영대 2003/03/05 4447 1208
489   [윈도우즈 API] 바탕화면, 시작메뉴 icon 숨기기  김영대 2003/03/07 5026 1207
488   [윈도우즈 API] 파일에서 아이콘 빼내기(16 * 16)  김영대 2003/03/07 4558 1207
487   [일반/컴포넌트] Zlib 를 이용한 압축과 해제  김영대 2004/08/03 5294 1206
486   [시스템] 파일 복사하면서 진행상태와 남은 시간 표시하기  김영대 2003/04/10 5911 1206
485   [일반/컴포넌트] PageControl의 TabSheet 마다 PopupMenu 두기  김영대 2003/03/31 5187 1206
484   [윈도우즈 API] shortcut 만들기 (desktop, 시작메뉴)  김영대 2003/03/04 4226 1206
483   [윈도우즈 API] "임시 인터넷 파일" 전부 지우기  김영대 2003/03/26 5104 1205
482   [윈도우즈 API] 외부 프로그램의 상태표시줄의 Text 가져오기  구창민 2003/03/14 5128 1205
481   [일반/컴포넌트] Memo의 입력 행수/열수 제한하기  김영대 2003/03/07 3656 1205
480   [일반/컴포넌트] 문자열 찾아서(Search) 바꾸기(Replace)  김영대 2003/03/04 4050 1204
479   [윈도우즈 API] '알려진 파일 형식의 파일 확장명 숨김' 여부  김영대 2004/08/25 4831 1203
  [시스템] ROM-BIOS 정보 읽기  김영대 2003/03/07 5153 1203
477   [데이터베이스] DB에 저장된 JPEG(JPG)를 DBGrid에 출력하기  김영대 2003/03/06 5052 1202
476   [시스템] Boot Drive 찾기  김영대 2003/03/04 4039 1201
475   [일반/컴포넌트] 윈도우즈 제어판 화면 띄우기  김영대 2003/03/07 4455 1200
474   [윈도우즈 API] 레지스트리(registry)를 바꾼후...  김영대 2003/03/05 4468 1200
473   [윈도우즈 API] 시스템 메뉴에서 menu item 제거하기  김영대 2003/03/05 4494 1199
472   [윈도우즈 API] 폼이 최대화될때 특정루틴 실행하기  김영대 2003/03/05 4279 1199
471   [일반/컴포넌트] ComboBox 가 drop down되었을때의 실제 크기는 얼마 ?  김영대 2003/03/04 4206 1199
470   [일반/컴포넌트] GIF 이미지의 width/height 구하기  김영대 2003/04/10 4507 1198
469   [윈도우즈 API] Control의 repainting 금지하여 깜박임 줄이기  김영대 2003/03/07 5129 1198
468   [윈도우즈 API] 관련파일 만들기(associate my application)  김영대 2003/03/05 4132 1198
467   [윈도우즈 API] 윈도우즈 "시작" 메뉴를 내 프로그램 안으로...  김영대 2003/03/07 4447 1197
466   [윈도우즈 API] 윈도우즈 전화걸기 화면 띄우기  김영대 2003/03/07 4509 1196
465   [윈도우즈 API] 프로그램으로 Screensaver 등록하는 두가지 방법  김영대 2003/03/06 4255 1196
464   [일반/컴포넌트] VB Left$(), Right$(), LTrim$() ...  김영대 2003/03/04 4841 1196
463   [시스템] IP Address 윈도우 폼위에 만들어보기  구창민 2003/03/14 5224 1195
462   [시스템] process 가 사용한 메모리 구하기  김영대 2003/03/07 4583 1194
461   [윈도우즈 API] EXE,DLL의 아이콘을 빼내서 다른곳에 사용하기  김영대 2003/03/05 4514 1193
460   [시스템] 마우스 포인터 속도 바꾸기  김영대 2003/03/06 4812 1192
459   [시스템] 메모리 부하(load)량 측정해 보기  김영대 2003/03/05 5080 1192
458   [윈도우즈 API] desktop 배경화면 바꾸기 예제  김영대 2003/03/05 4692 1192
457   [윈도우즈 API] 화면의 Memo 가 보여줄 수 있는 라인수 알아내기  김영대 2003/03/05 4822 1192
456   [윈도우즈 API] Locale 정보 얻기  김영대 2003/03/04 4170 1192
455   [일반/컴포넌트] PageControl 에서 미리 이동하려는 Sheet 알아내어 제어하기  김영대 2004/09/13 4747 1191
454   [네트웍/인터넷] 모든 TCP/IP interfaces 의 IP/Netmask 구하기  김영대 2003/03/07 5407 1191
453   [일반/컴포넌트] RichEdit에서 HTML 태그를 다른색으로 표시하기  김영대 2003/03/07 5397 1191
452   [시스템] OS가 NT인지 95인지 판단하려면...  김영대 2003/03/06 4167 1191

[이전 10개] [1]..[11][12] 13 [14][15][16][17][18][19][20]..[25] [다음 10개]
 

Copyright 1999-2020 Zeroboard / skin by zero