evmar / retrowin32

windows emulator
https://evmar.github.io/retrowin32/
Apache License 2.0
587 stars 26 forks source link

Getting PocoMan.exe running #43

Open LinusU opened 2 months ago

LinusU commented 2 months ago

This issue is to track my work in getting PocoMan v4.0 running. See more background in #39

My working branch is here: https://github.com/LinusU/retrowin32/tree/pocoman

evmar commented 2 months ago

This looks great so far, thanks for breaking it up! Also if you need any help or advice looking into your binary please reach out. I would love to flesh out the docs with anything useful for you.

evmar commented 2 months ago

By the way, if a function is missing, retrowin32 logs a warning about it but keeps running. So it will only matter if your binary actually calls it (where it will crash after calling a null pointer). Functions like ExitThread might not matter until you get to the end of the program, not sure.

LinusU commented 2 months ago

It seems like the threading is just used to kick of one thread that connects to the internet and check some kind of latest news, stores it in the registry, and then exits. Without ExitThread it crashed the entire program when it tried to exit, which since I've only stubbed the internet connect functions is basically immediately 😅

I'm at a point now where the program can run more than a few milliseconds! Unfortunately, it's a very tiny window, and something with the rendering seems broken. And as soon as I press anywhere it tries to call WideCharToMultiByte.

Screenshot 2024-09-14 at 23 30 00
INFO win32/src/winapi/user32/window.rs:324 user32/window/CreateWindowExA(dwExStyle:Err(300), lpClassName:Name("PocoMan Class"), lpWindowName:Some("PocoMan"), dwStyle:Ok(BORDER | DLGFRAME | SYSMENU | GROUP), X:80000000, Y:0, nWidth:1, nHeight:1, hWndParent:HANDLE(0), hMenu:0, hInstance:400000, lpParam:0) -> HANDLE(1)

This part seems a bit suspicious: X:80000000, Y:0, nWidth:1, nHeight:1

evmar commented 2 months ago

One idea is you could maybe locally stub out the CreateThread impl such that it never starts the thread in the first place, just to see what happens next. Depends on if it waits for the thread to come back though. I saw in your above list you had CreateEvent/SetEvent which are typically used for thread synchronization...


I ran the installer (via wine 😊 ) and I get this:

     Running `target/debug/retrowin32 --win32-trace - '/Users/evmar/.wine/drive_c/Program Files (x86)/PocoMan/pocoman.exe'`
WARN win32/src/winapi/kernel32/dll.rs:216 load_library("wsock32.dll"): not found
WARN win32/src/winapi/kernel32/misc.rs:152 IsProcessorFeaturePresent(Ok(FLOATING_POINT_PRECISION_ERRATA)) => false
thread 'main' panicked at x86/src/ops/basic.rs:478:16:
attempt to shift left with overflow

I guess I need more of your patches?


The window size could indicate a problem, but also some apps I've seen create a window with an unknown size, then resize it later in response to some window messages. ... since I have the exe anyway I checked and that is what it does.

ghidra says:

      g_hwnd = CreateWindowExA(0x300,s_PocoMan_Class_00418174,s_PocoMan_00418184,0xca0000,local_14,
                               local_18,1,1,(HWND)0x0,(HMENU)0x0,pHVar2,(LPVOID)0x0);

and there is some function called by the wndproc that does

  GetWindowRect(g_hwnd,&local_3c);
  GetClientRect(g_hwnd,&tStack_4c);
  SetWindowPos(g_hwnd,(HWND)0x0,0,0, ...
evmar commented 2 months ago

My above crash is fixed in 6427aededc6dc4e161b2ad9a93788af3e342e0c5, which now reveals the actual problem was running from the wrong directory

MessageBox: PocoMan
Can't open PocoMan.map for reading
evmar commented 2 months ago

If it helps you any, feel free to send a PR that is just like "here are all the stubs I need". You don't need to send separate PRs for them if it's too much effort. (I'm fine with separate PRs, just trying to save you some effort...)

LinusU commented 2 months ago

Sorry, I have had limited with time today and just cherry picked som already done work. Will give some more time to the comments later!

If it helps you any, feel free to send a PR that is just like "here are all the stubs I need"

Hehe, yeah I realized that that would probably have been a good strategy 😅

At this point I think that they are all merged though! Well, as far as I have gotten at least, will probably be a bit more when I get more time to dig further!

Thanks for all the help, and for looking into the binary yourself! As said, I'll try to read thru all your comments properly and answer in a few days

LinusU commented 2 months ago

Making progress!

Screenshot 2024-09-22 at 17 19 28

Updating SetWindowPos to actually resize the host window fixed the sizing issue!

Need to implement stretching in StretchDIBits now 🐎

...and it seems like there are some flags to flip/rotate tiles that needs to be implemented

LinusU commented 2 months ago

I'm running into a really strange problem. For some reason, the application is closing its file handle whilst in the middle of reading from it. It's reading the map data, and since it then errors out goes into an error state. Making CloseHandle a no-op fixes the issue:

diff --git a/win32/src/winapi/kernel32/file.rs b/win32/src/winapi/kernel32/file.rs
index 79e7b8a1..c8a6946f 100644
--- a/win32/src/winapi/kernel32/file.rs
+++ b/win32/src/winapi/kernel32/file.rs
@@ -382,7 +382,7 @@ pub fn SetFilePointer(
         lDistanceToMove |= (**high as i64) << 32;
     }
     let Some(file) = machine.state.kernel32.files.get_mut(hFile) else {
-        log::debug!("SetFilePointer({hFile:?}) unknown handle");
+        log::warn!("SetFilePointer({hFile:?}) unknown handle");
         set_last_error(machine, ERROR_INVALID_HANDLE);
         return u32::MAX;
     };
diff --git a/win32/src/winapi/kernel32/misc.rs b/win32/src/winapi/kernel32/misc.rs
index d6d06472..49de0c85 100644
--- a/win32/src/winapi/kernel32/misc.rs
+++ b/win32/src/winapi/kernel32/misc.rs
@@ -279,6 +279,7 @@ pub fn FormatMessageW(

 #[win32_derive::dllexport]
 pub fn CloseHandle(machine: &mut Machine, hObject: HFILE) -> bool {
+    return true; // FIXME: remove this line
     if machine.state.kernel32.files.remove(hObject).is_none() {
         log::debug!("CloseHandle({hObject:?}): unknown handle");
         set_last_error(machine, ERROR_INVALID_HANDLE);

It's happening right after the call to InternetOpenA, which I think is called by a background thread. I'm wondering if the file handles are somehow interfering with each other from the different threads 🤔

Reading the disassembled source, there are calls to InternetCloseHandle at the end of the function that runs in the background thread...

LinusU commented 2 months ago

With the CloseHandle patch from last comment, the first map now loads! 🙌

Screenshot 2024-09-22 at 19 06 40

(something seems broken with the time in the titlebar, I just started the game in the screenshot)

evmar commented 2 months ago

Re time, I just monkeyed with it here, maybe I got something wrong: https://github.com/evmar/retrowin32/commit/0c3e48507dc5f6424d5b10b6e230046a4c2095cd

Re CloseHandle, I looked at the places where it's called in pocoman and most seemed to be error paths. But the one at address 00410419 might be it(?).

I wrote all of the threading-related stubs before retrowin32 supported threads so they are all definitely wrong. I see some use of thread-local storage in pocoman and that also is certainly wrong right now, not sure how serious it is. The function passed to CreateThread immediately calls TlsSetValue, probably to stash some sort of "current state" object.

evmar commented 2 months ago

Hm wait, the call at 0040c77d seems to be the thread function shutting down and is closing a handle found in the Tls, that seems very likely it.

evmar commented 2 months ago

(moving TLS discussion to #70 to not clutter this up)

evmar commented 1 month ago

With https://github.com/evmar/retrowin32/commit/8c1713aff5eedd229aaf1319adb5d96801ae27c2 it now plays "I am pocoman" audio on startup. Might get annoying, we can put it behind a flag if so!

LinusU commented 1 month ago

With 8c1713a it now plays "I am pocoman" audio on startup. Might get annoying, we can put it behind a flag if so!

That's amazing!! 🤩

Although it seems like the audio then underflows and crashes the entire application 😅

WARN cli/src/sdl.rs:319 audiobuf underflow
thread '<unnamed>' panicked at cli/src/sdl.rs:315:34:
range start index 44544 out of range for slice of length 44100
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
libc++abi: terminating due to uncaught foreign exception
zsh: abort      cargo run -p retrowin32 -F x86-emu,sdl -- --win32-trace '*' PocoMan.exe
evmar commented 1 month ago

Oh no! I pushed another commit to disable it by default until I have it all working, sorry!

evmar commented 1 month ago

I added a --audio flag to the command line, and verified that after clicking through the first screen audio continues to work on the map if you click around. I think with StretchBlt and some keyboard support this would be pretty solid.

evmar commented 1 month ago

The bitmap code is still gross but I made it a lot less gross in https://github.com/evmar/retrowin32/commit/af32dd4fa1c63638c8dfae5d23db5c2a2e5c85c2 , with a special eye to making StretchBlt easy to implement.

evmar commented 1 month ago
image
evmar commented 1 month ago
image

sample program in exe/rust/bin/dib.rs that runs some of these functions

evmar commented 1 month ago

oh and i can run it in wine to compare!

image
evmar commented 1 month ago

https://github.com/evmar/retrowin32/commit/ccdac7115cf39068048feb93a9ac7ed180113a6a

image
evmar commented 1 month ago

Implemented some missing file stuff on the web site, now runs in my browser!

image