diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 09bf06a2..3bbc6d50 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,10 +1,6 @@ name: Build -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] +on: [push, pull_request] jobs: build: @@ -61,6 +57,27 @@ jobs: cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -G "NMake Makefiles" cmake --build . + - name: Upload Artifact + uses: actions/upload-artifact@master + with: + name: Win32 + path: | + build/ISLE.EXE + build/ISLE.PDB + build/LEGO1.DLL + build/LEGO1.PDB + + compare: + needs: build + runs-on: windows-latest + steps: + - uses: actions/checkout@master + + - uses: actions/download-artifact@master + with: + name: Win32 + path: build + - name: Restore cached original binaries id: cache-original-binaries uses: actions/cache/restore@v3 @@ -103,14 +120,6 @@ jobs: run: | python3 tools/verexp/verexp.py legobin/LEGO1.DLL build/LEGO1.DLL - - name: Upload Artifact - uses: actions/upload-artifact@master - with: - name: Win32 - path: | - build/ISLE.EXE - build/LEGO1.DLL - - name: Upload Artifact uses: actions/upload-artifact@master with: @@ -118,15 +127,30 @@ jobs: path: | ISLEPROGRESS.* LEGO1PROGRESS.* - + + upload: + needs: [build, compare] + runs-on: ubuntu-latest + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + steps: + - uses: actions/checkout@v3 + with: + repository: 'probonopd/uploadtool' + + - uses: actions/download-artifact@master + with: + name: Win32 + path: build + + - uses: actions/download-artifact@master + with: + name: Accuracy Report + - name: Upload Continuous Release - shell: bash - if: github.event_name == 'push' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} UPLOAD_KEY: ${{ secrets.UPLOAD_KEY }} run: | - curl -fLOSs https://raw.githubusercontent.com/probonopd/uploadtool/master/upload.sh ./upload.sh \ build/ISLE.EXE \ build/LEGO1.DLL \ diff --git a/LEGO1/legoinputmanager.h b/LEGO1/legoinputmanager.h index 6bf3db4b..74a2a8fa 100644 --- a/LEGO1/legoinputmanager.h +++ b/LEGO1/legoinputmanager.h @@ -1,9 +1,8 @@ #ifndef LEGOINPUTMANAGER_H #define LEGOINPUTMANAGER_H -#include "mxpresenter.h" - #include "decomp.h" +#include "mxpresenter.h" enum NotificationId { diff --git a/LEGO1/mxbitmap.cpp b/LEGO1/mxbitmap.cpp index a7425dd8..2ccdd80b 100644 --- a/LEGO1/mxbitmap.cpp +++ b/LEGO1/mxbitmap.cpp @@ -14,15 +14,75 @@ MxBitmap::MxBitmap() // OFFSET: LEGO1 0x100bca10 MxBitmap::~MxBitmap() { - if (this->m_info != NULL) { + if (this->m_info != NULL) delete m_info; - } - if (this->m_data != NULL) { + if (this->m_data != NULL) delete m_data; - } - if (this->m_palette != NULL) { + if (this->m_palette != NULL) delete m_palette; - } +} + +// OFFSET: LEGO1 0x100bcc40 STUB +int MxBitmap::vtable14(int) +{ + return 0; +} + +// OFFSET: LEGO1 0x100bcba0 STUB +int MxBitmap::vtable18(BITMAPINFOHEADER *p_bmiHeader) +{ + return 0; +} + +// OFFSET: LEGO1 0x100bcaa0 STUB +int MxBitmap::vtable1c(int p_width, int p_height, MxPalette *p_palette, int) +{ + return 0; +} + +// OFFSET: LEGO1 0x100bcd60 STUB +MxResult MxBitmap::LoadFile(HANDLE p_handle) +{ + return SUCCESS; +} + +// OFFSET: LEGO1 0x100bcd10 +long MxBitmap::Read(const char *p_filename) +{ + MxResult result = FAILURE; + HANDLE handle = CreateFileA( + p_filename, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL + ); + + if (handle != INVALID_HANDLE_VALUE && !LoadFile(handle)) + result = SUCCESS; + + if (handle) + CloseHandle(handle); + + return result; +} + +// OFFSET: LEGO1 0x1004e0d0 +int MxBitmap::vtable28(int) +{ + return -1; +} + +// OFFSET: LEGO1 0x100ce70 STUB +void MxBitmap::vtable2c(int, int, int, int, int, int, int) +{ +} + +// OFFSET: LEGO1 0x100d020 STUB +void MxBitmap::vtable30(int, int, int, int, int, int, int) +{ } // OFFSET: LEGO1 0x100bd1c0 @@ -51,24 +111,19 @@ MxPalette *MxBitmap::CreatePalette() return pal; } -// OFFSET: LEGO1 0x100bcd10 -long MxBitmap::Read(const char *filename) +// OFFSET: LEGO1 0x100bd280 STUB +void MxBitmap::vtable38(void*) { - HANDLE handle; - int unk1; - MxResult ret = FAILURE; - - handle = CreateFileA(filename,GENERIC_READ,FILE_SHARE_READ,(LPSECURITY_ATTRIBUTES)NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,(HANDLE)NULL); - if(handle != (HANDLE)INVALID_HANDLE_VALUE) { // INVALID_HANDLE_VALUE = -1, or 0xffffffff - // FIXME: line 16. iVar gets changed in this line - if(unk1 == 0) { - ret = SUCCESS; - } - } - if(handle != (HANDLE)NULL) { - CloseHandle(handle); - } - - return ret; } +// OFFSET: LEGO1 0x100bd2d0 STUB +int MxBitmap::vtable3c(MxBool) +{ + return 0; +} + +// OFFSET: LEGO1 0x100bd3e0 STUB +int MxBitmap::vtable40(HDC p_hdc, int p_xSrc, int p_ySrc, int p_xDest, int p_yDest, int p_destWidth, int p_destHeight) +{ + return 0; +} \ No newline at end of file diff --git a/LEGO1/mxbitmap.h b/LEGO1/mxbitmap.h index 24d9e166..e16fcb0e 100644 --- a/LEGO1/mxbitmap.h +++ b/LEGO1/mxbitmap.h @@ -11,9 +11,21 @@ class MxBitmap : public MxCore { public: __declspec(dllexport) MxBitmap(); - __declspec(dllexport) virtual ~MxBitmap(); - __declspec(dllexport) virtual MxPalette *CreatePalette(); - __declspec(dllexport) virtual long Read(const char *); + __declspec(dllexport) virtual ~MxBitmap(); // vtable+00 + + virtual int vtable14(int); + virtual int vtable18(BITMAPINFOHEADER *p_bmiHeader); + virtual int vtable1c(int p_width, int p_height, MxPalette *p_palette, int); + virtual MxResult LoadFile(HANDLE p_handle); + __declspec(dllexport) virtual long Read(const char *p_filename); // vtable+24 + virtual int vtable28(int); + virtual void vtable2c(int, int, int, int, int, int, int); + virtual void vtable30(int, int, int, int, int, int, int); + __declspec(dllexport) virtual MxPalette *CreatePalette(); // vtable+34 + virtual void vtable38(void*); + virtual int vtable3c(MxBool); + virtual int vtable40(HDC p_hdc, int p_xSrc, int p_ySrc, int p_xDest, int p_yDest, int p_destWidth, int p_destHeight); + private: BITMAPINFO *m_info; BITMAPINFOHEADER *m_bmiHeader; diff --git a/tools/reccmp/reccmp.py b/tools/reccmp/reccmp.py index abfe71a7..226be15d 100755 --- a/tools/reccmp/reccmp.py +++ b/tools/reccmp/reccmp.py @@ -6,6 +6,7 @@ import difflib import struct import subprocess +import logging import os import sys import colorama @@ -24,8 +25,14 @@ parser.add_argument('--svg-icon', metavar='icon', help='Icon to use in SVG (PNG)') parser.add_argument('--print-rec-addr', action='store_true', help='Print addresses of recompiled functions too') +parser.set_defaults(loglevel=logging.INFO) +parser.add_argument('--debug', action='store_const', const=logging.DEBUG, dest='loglevel', help='Print script debug information') + args = parser.parse_args() +logging.basicConfig(level=args.loglevel, format='[%(levelname)s] %(message)s') +logger = logging.getLogger(__name__) + colorama.init() verbose = None @@ -61,6 +68,7 @@ # to file addresses class Bin: def __init__(self, filename): + logger.debug('Parsing headers of "%s"... ', filename) self.file = open(filename, 'rb') #HACK: Strictly, we should be parsing the header, but we know where @@ -68,15 +76,16 @@ def __init__(self, filename): # Read ImageBase self.file.seek(0xB4) - self.imagebase = struct.unpack('i', self.file.read(4))[0] + self.imagebase, = struct.unpack(' str: + if unix_fn.startswith('./'): + return self.win_cmd + '\\' + unix_fn[2:].replace('/', '\\') + if unix_fn.startswith(self.unix_cwd): + return self.win_cwd + '\\' + unix_fn.removeprefix(self.unix_cwd).replace('/', '\\').lstrip('\\') + return self._call_winepath_unix2win(unix_fn) + + def get_unix_path(self, win_fn: str) -> str: + if win_fn.startswith('.\\') or win_fn.startswith('./'): + return self.unix_cwd + '/' + win_fn[2:].replace('\\', '/') + if win_fn.startswith(self.win_cwd): + return self.unix_cwd + '/' + win_fn.removeprefix(self.win_cwd).replace('\\', '/') + return self._call_winepath_win2unix(win_fn) + + @staticmethod + def _call_winepath_unix2win(fn: str) -> str: + return subprocess.check_output(['winepath', '-w', fn], text=True).strip() + + @staticmethod + def _call_winepath_win2unix(fn: str) -> str: + return subprocess.check_output(['winepath', fn], text=True).strip() def get_file_in_script_dir(fn): return os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), fn) @@ -109,22 +140,24 @@ class SymInfo: funcs = {} lines = {} - def __init__(self, pdb, file): + def __init__(self, pdb, file, wine_path_converter): call = [get_file_in_script_dir('cvdump.exe'), '-l', '-s'] - if os.name != 'nt': + if wine_path_converter: # Run cvdump through wine and convert path to Windows-friendly wine path call.insert(0, 'wine') - call.append(get_wine_path(pdb)) + call.append(wine_path_converter.get_wine_path(pdb)) else: call.append(pdb) - print('Parsing %s...' % pdb) - + logger.info('Parsing %s ...', pdb) + logger.debug('Command = %r', call) line_dump = subprocess.check_output(call).decode('utf-8').split('\r\n') current_section = None + logger.debug('Parsing output of cvdump.exe ...') + for i, line in enumerate(line_dump): if line.startswith('***'): current_section = line[4:] @@ -132,8 +165,6 @@ def __init__(self, pdb, file): if current_section == 'SYMBOLS' and 'S_GPROC32' in line: addr = int(line[26:34], 16) - - info = RecompiledInfo() info.addr = addr + recompfile.imagebase + recompfile.textvirt @@ -155,9 +186,9 @@ def __init__(self, pdb, file): elif current_section == 'LINES' and line.startswith(' ') and not line.startswith(' '): sourcepath = line.split()[0] - if os.name != 'nt': + if wine_path_converter: # Convert filename to Unix path for file compare - sourcepath = get_unix_path(sourcepath) + sourcepath = wine_path_converter.get_unix_path(sourcepath) if sourcepath not in self.lines: self.lines[sourcepath] = {} @@ -178,18 +209,23 @@ def __init__(self, pdb, file): j += 1 + logger.debug('... Parsing output of cvdump.exe finished') + def get_recompiled_address(self, filename, line): addr = None found = False - #print('Looking for ' + filename + ' line ' + str(line)) + logger.debug('Looking for %s:%d', filename, line) for fn in self.lines: # Sometimes a PDB is compiled with a relative path while we always have # an absolute path. Therefore we must - if os.path.samefile(fn, filename): - filename = fn - break + try: + if os.path.samefile(fn, filename): + filename = fn + break + except FileNotFoundError as e: + continue if filename in self.lines and line in self.lines[fn]: addr = self.lines[fn][line] @@ -197,13 +233,16 @@ def get_recompiled_address(self, filename, line): if addr in self.funcs: return self.funcs[addr] else: - print('Failed to find function symbol with address: %s' % hex(addr)) + logger.error('Failed to find function symbol with address: 0x%x', addr) else: - print('Failed to find function symbol with filename and line: %s:%s' % (filename, str(line))) + logger.error('Failed to find function symbol with filename and line: %s:%d', filename, line) +wine_path_converter = None +if os.name != 'nt': + wine_path_converter = WinePathConverter(source) origfile = Bin(original) recompfile = Bin(recomp) -syminfo = SymInfo(syms, recompfile) +syminfo = SymInfo(syms, recompfile, wine_path_converter) print()