Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get poco-w95.exe running #39

Closed
wants to merge 10 commits into from
Closed

Get poco-w95.exe running #39

wants to merge 10 commits into from

Conversation

LinusU
Copy link
Contributor

@LinusU LinusU commented Sep 11, 2024

Hello! 👋

This is my first contribution so I'm sure that I got a lot of things wrong! 🙈

First of all, I just want to say that I think that this is a really cool project, and as someone who has worked on a gameboy emulator in Rust before, I like that this takes a similar approach. I'm currently trying to get the classic game Pocoman working.

Opening this for some early feedback since I've already made some changes to existing functions in kernel32.dll and I think that it would be great to get some feedback on that, and maybe even get those changes merged as I continue on with implementing more functions.

The changes to GetStartupInfoA was necessary since some garbage pointers was returned in cbReserved2 and lpReserved2 otherwise, which made the game crash with an oob error otherwise.

This is the binary I'm trying to get running: https://www.sleepless.com/pocoman/poco-w95.exe

Relevant documentation:

@LinusU LinusU changed the title Get pocoman-w95.exe running Get poco-w95.exe running Sep 11, 2024
@evmar
Copy link
Owner

evmar commented Sep 11, 2024

(On phone sorry for brevity)

This looks great so far! I will give a more thorough review when I get to a computer.

The changes look pretty right to me, but I expected shell32.rs to be empty, not contain generated code (?)

Re the startup info, I think the way the api is supposed to work is the caller provides the size of the struct, because the struct changed size in different windows versions and you want to be sure to only fill in the part they actually allocated space for. However as you observed I am not sure what was supposed to happen when the size param is zero, hm. Maybe there is some default minimum size to use or something like that? I will investigate when I get the chance.

Really cool you’re getting this working!

@LinusU
Copy link
Contributor Author

LinusU commented Sep 12, 2024

The changes look pretty right to me, but I expected shell32.rs to be empty, not contain generated code (?)

This is indeed interesting. I had a hard time figuring out how to add a new dll at all, and I thought that I created the shell32.rs file as just an empty file in the beginning. Removing the content and running the make files again doesn't seem to add it back so I'm starting to wonder if I just accidentally pasted it from builtin.rs 😅

I'll remove it 👍

Re the startup info, I think the way the api is supposed to work is the caller provides the size of the struct, because the struct changed size in different windows versions and you want to be sure to only fill in the part they actually allocated space for. However as you observed I am not sure what was supposed to happen when the size param is zero, hm. Maybe there is some default minimum size to use or something like that? I will investigate when I get the chance.

I'll try to find some more info on this as well!


How would you prefer that I structure my work forward? I'm thinking now that it might have been better if this would have been two or three pull requests (one for GetStartupInfoA, one for SizeofResource, and possibly one for the stub shell32 but might be better to have that commit together with my first function implemented in it instead).

I think that maybe I would prefer having an issue open where I track my progress with links to PRs and list of my next steps. And then I'll submit PRs for each isolated change and rebase my working branch on your main branch after each PR merged. To put it more concretely: I would open a PR for GetStartupInfoA and one for SizeofResource. My next todo is advapi32.dll:RegCreateKeyA, so that would be the next PR which should also be free standing. If I do anything else that builds on e.g. the changes in the SizeofResource PR that would live in my branch until that has been merged, and then I'll open a PR for that.

I see a few benefits with this:

  • I can get things merged upstream quickly which hopefully helps you and other users
  • I can get feedback on the work I do quickly, so cascading changes are minimized
  • I would keep the PRs reasonably small, which should help your reviewing work, and make me responsible for updating work in progress code that I haven't yet submitted

Does this sounds good to you?

@evmar
Copy link
Owner

evmar commented Sep 12, 2024

I'm happy to merge as you go, with whatever works best for you. Your proposal is sensible. My only caveat is that I have been really short on time lately (kid stuff), I am super embarrassingly behind on another PR that I am close to finally merging. I'd be happy to merge this as you go.

@@ -77,18 +81,18 @@ pub fn find_resource<'a>(
hInstance: HINSTANCE,
typ: ResourceKey<&Str16>,
name: ResourceKey<&Str16>,
) -> Option<&'a [u8]> {
) -> Option<Range<usize>> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This range is a span within the 32-bit memory right? For consistency I've been using u32s for these, which helps a bit with keeping straight which kind of things we're talking about. On wasm usize=u32 anyway but on other platforms it's 32-bit vs 64-bit.

Comment on lines 149 to 153
let image = mem.slice(hInstance..);

let resource = &image[handle.1.clone()];

machine.mem().offset_of(resource.as_ptr())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I followed this math, this could be just hInstance + handle.1, right?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to get all the pointers straight:

  • pe::find_resource returns a range that is within the image
  • maybe kernel32::find_resource should return a range that is within the process memory (so with hInstance already added)?
  • so then this would only need to return handle.1?

@@ -75,6 +75,8 @@ fn load_bitmap(
ResourceKey::Id(pe::RT::BITMAP as u32),
name,
)?;
let image = machine.mem().slice(hInstance..);
let buf = &image[buf];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... and if find_resource returns Range, this can be something like mem().slice(range) or so?

let image = mem.slice(hInstance..);
if hInstance == kernel32.image_base {
let section = kernel32.resources.as_slice(image)?;
Some(&image[pe::find_resource(section, typ.into_pe(), name.into_pe())?])
pe::find_resource(section, typ.into_pe(), name.into_pe())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I'm looking, I wonder if pe::find_resource ought to return a Range as well, hm. If you look at its impl you can see it has u32s and it only casts to usize on the way out.

@@ -322,8 +322,11 @@ unsafe impl ::memory::Pod for STARTUPINFOA {}
pub fn GetStartupInfoA(_machine: &mut Machine, lpStartupInfo: Option<&mut STARTUPINFOA>) -> u32 {
// MSVC runtime library passes in uninitialized memory for lpStartupInfo, so don't trust info.cb.
let info = lpStartupInfo.unwrap();
let len = std::cmp::min(info.cb, std::mem::size_of::<STARTUPINFOA>() as u32);
let len = std::mem::size_of::<STARTUPINFOA>() as u32;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, I checked and I now believe that STARTUPINFOA has never changed in size, so my worries about callers passing in an undersized buffer or something like that are unfounded. And further I checked and at least win2k's kernel32.dll does exactly what you've done here, so I think this is correct.

@LinusU
Copy link
Contributor Author

LinusU commented Sep 13, 2024

I have now managed to run enough of the program to realize that I'm running an installer, and not the actual game 😂

As I've understand GetStartupInfoA, the caller always passes a pointer to uninitialized memory, and the function will then fill in the values. Before this change, GetStartupInfoA read the value of cb as an input parameter to get the length of the struct. In my program that part of the memory was uninitialized and happened to be 0, so the rest of the values were never zeroed out, leading to garbage data being returned in the other field.

This change make sure to zero out the entire struct, and then sets cb to the actual length of the struct.
@LinusU
Copy link
Contributor Author

LinusU commented Sep 13, 2024

Thanks for the comments! I've adressed them and also implemented my plan resulting in the following things that replaces this PR:

@LinusU LinusU closed this Sep 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants